# SEE modeldata package for new datasets
library(tidyverse)         # for graphing and data cleaning
library(tidymodels)        # for modeling
library(stacks)            # for stacking models
library(naniar)            # for examining missing values (NAs)
library(lubridate)         # for date manipulation
library(moderndive)        # for King County housing data
library(DALEX)             # for model interpretation  
library(DALEXtra)          # for extension of DALEX
library(patchwork)         # for combining plots nicely
library(grid)
library(gridExtra)
library(ggtext)
library(dbplyr)            # for SQL query "cheating" - part of tidyverse but needs to be loaded separately
library(mdsr)              # for accessing some databases - goes with Modern Data Science with R textbook
library(RMySQL)            # for accessing MySQL databases
library(RSQLite)           # for accessing SQLite databases
library(kableExtra)

#mapping
library(maps)              # for built-in maps
library(sf)                # for making maps using geom_sf
library(ggthemes)          # Lisa added - I like theme_map() for maps :)
library(viridis)

#tidytext
library(tidytext)          # for text analysis, the tidy way!
library(textdata)          
library(reshape2)
library(wordcloud)         # for wordcloud
library(stopwords)

theme_set(theme_minimal()) # Lisa's favorite theme

When you finish the assignment, remove the # from the options chunk at the top, so that messages and warnings aren’t printed. If you are getting errors in your code, add error = TRUE so that the file knits. I would recommend not removing the # until you are completely finished.

Put it on GitHub!

From now on, GitHub should be part of your routine when doing assignments. I recommend making it part of your process anytime you are working in R, but I’ll make you show it’s part of your process for assignments.

Task: When you are finished with the assignment, post a link below to the GitHub repo for the assignment. If you want to post it to your personal website, that’s ok (not required). Make sure the link goes to a spot in the repo where I can easily find this assignment. For example, if you have a website with a blog and post the assignment as a blog post, link to the post’s folder in the repo. As an example, I’ve linked to my GitHub stacking material here.

https://github.com/thytng/stat494-assignment3

Local Interpretable Machine Learning

You are going to use the King County house data and the same random forest model to predict log_price that I used in the tutorial.

# Load in the data
data("house_prices")

# Create log_price and drop price variable
house_prices <- house_prices %>% 
  mutate(log_price = log(price, base = 10)) %>% 
  # make all integers numeric ... fixes prediction problem
  mutate(across(where(is.integer), as.numeric)) %>% 
  select(-price)
set.seed(327) #for reproducibility

# Randomly assigns 75% of the data to training.
house_split <- initial_split(house_prices, 
                             prop = .75)
house_training <- training(house_split)
house_testing <- testing(house_split)

# Set up recipe and transformation steps and roles
ranger_recipe <- 
  recipe(formula = log_price ~ ., 
         data = house_training) %>% 
  step_date(date, 
            features = "month") %>% 
  # Make these evaluative variables, not included in modeling
  update_role(all_of(c("id",
                       "date")),
              new_role = "evaluative")

# Define model
ranger_spec <- 
  rand_forest(mtry = 6, 
              min_n = 10, 
              trees = 200) %>% 
  set_mode("regression") %>% 
  set_engine("ranger")

# Create workflow
ranger_workflow <- 
  workflow() %>% 
  add_recipe(ranger_recipe) %>% 
  add_model(ranger_spec) 

# Fit the model
set.seed(712) # for reproducibility - random sampling in random forest choosing number of variables
ranger_fit <- ranger_workflow %>% 
  fit(house_training)

# Create an explainer
rf_explain <- 
  explain_tidymodels(
    model = ranger_fit,
    data = house_training %>% select(-log_price), 
    y = house_training %>%  pull(log_price),
    label = "rf"
  )
## Preparation of a new explainer is initiated
##   -> model label       :  rf 
##   -> data              :  16210  rows  20  cols 
##   -> data              :  tibble converted into a data.frame 
##   -> target variable   :  16210  values 
##   -> predict function  :  yhat.workflow  will be used (  default  )
##   -> predicted values  :  No value for predict function target column. (  default  )
##   -> model_info        :  package tidymodels , ver. 0.1.2 , task regression (  default  ) 
##   -> predicted values  :  numerical, min =  5.057808 , mean =  5.665083 , max =  6.722883  
##   -> residual function :  difference between y and yhat (  default  )
##   -> residuals         :  numerical, min =  -0.3314416 , mean =  0.0004168269 , max =  0.2491147  
##   A new explainer has been created! 

Tasks:

  1. Choose 3 new observations and do the following for each observation:
obs <- house_testing %>% sample_n(size=3)
  • Construct a break-down plot using the default ordering. Interpret the resulting graph. Which variables contribute most to each observation’s prediction?
for (i in 1:3) {
  new_obs <- obs %>% slice(i)
  # Pulls together the data needed for the break-down plot
  pp_rf <- predict_parts(explainer = rf_explain,
                         new_observation = new_obs,
                         type = "break_down")
  
  # Break-down plot
  bnum <- paste("b", i, sep="")
  b <- plot(pp_rf)
  assign(bnum, b)
}

grid.arrange(b1, b2, b3, ncol=3)

lat and sqft_living contribute most to each observation’s prediction. For the first and second observations, grade=6 (or 7 since the function somehow transformed this value during the process) is the third most “important” covariates. For the third one, it is sqft_living15.

  • Construct a SHAP graph and interpret it. Does it tell a similar story to the break-down plot?
for (i in 1:3) {
  new_obs <- obs %>% slice(i)
  rf_shap <-predict_parts(explainer = rf_explain,
                          new_observation = new_obs,
                          type = "shap",
                          B = 10) 
  snum <- paste("s", i, sep="")
  s <- plot(rf_shap)
  assign(snum, s)
}

grid.arrange(s1, s2, s3, ncol=3)

The plots do seem to agree. Once again, lat and sqft_living come out as the variables with the biggest contribution for all three observations. The plots correctly display grade=7 as the third most important covariate for the first two. For the third observation, long and sqft_living15 are the third and fourth most important respectively, but in the breakdown plot their order of importance is reversed.

  • Construct a LIME graph (follow my code carefully). How close is each original prediction to the prediction from the local model? Interpret the result. You can also try using fewer or more variables in the local model than I used in the example.
set.seed(646)

preds <- data.frame(model_r2 = double(), model_prediction = double(), prediction = double())

for (i in 1:3) {
  new_obs <- obs %>% slice(i)
  
  model_type.dalex_explainer <- DALEXtra::model_type.dalex_explainer
  predict_model.dalex_explainer <- DALEXtra::predict_model.dalex_explainer
  
  lime_rf <- predict_surrogate(explainer = rf_explain,
                               new_observation = new_obs %>%
                                 select(-log_price), 
                               n_features = 5,
                               n_permutations = 1000,
                               type = "lime")
  preds <- rbind(preds, lime_rf %>% 
                   select(model_r2, model_prediction, prediction) %>% 
                   distinct())
  lnum <- paste("l", i, sep="")
  l <- plot(lime_rf) +
    labs(x = "Variable")
  assign(lnum, l)
}

The local predictions are quite close to the originals. The predictions are very close for observation 1, and they differ about .1 and .2 units for the second and third observation respectively. The LIME plots display a similar set of variables that are considered most important for each observation to the ones we saw from the break-down and SHAP plots. Each predictor has a different weight for each observation, meaning that it can have a positive or negative relationship with the response depending on the observation.

  1. Describe how you would use the interpretable machine learning tools we’ve learned (both local and global) in future machine learning projects? How does each of them help you?

These tools are excellent for showing how predictors contribute to the model. It is very likely that I will fit models that are more complex than regular linear or logistic regression and in my experience their interpretation is really technical and not intuitive at all. What often happens is that I would have a model and some measure to gauge the predictors’ contribution (i.e. coefficient values, variable importance, etc.), but I would have a difficult time translating that to results that I could understand, let alone communicate to a broader audience. Global interpretation tools make it easier to see the relationship between the response and a given covariate and how changing the latter would directly affect the value of the former. With local interpretation tools, I could visualize the contribution of each variable to an individual observation. Ultimately, these tools would help me understand a model and its components better and provide more intuitive and approachable interpretations.

SQL

You will use the airlines data from the SQL database that I used in the example in the tutorial. Be sure to include the chunk to connect to the database here. And, when you are finished, disconnect. You may need to reconnect throughout as it times out after a while.

con_air <- dbConnect_scidb("airlines")

Tasks:

  1. Create a SQL chunk and an equivalent R code chunk that does the following: for each airport (with its name, not code), year, and month find the total number of departing flights, the distinct destinations to which they flew, the average distance of the flight, and the proportion of flights that arrived more than 20 minutes late. In the R code chunk, write this out to a dataset. (HINT: 1. start small! 2. you may want to do the R part first and use it to “cheat” into the SQL code).
SELECT
  name,
  faa,
  month, 
  n_dep_flights,
  avg_distance,
  prop_late_over20
FROM (
  SELECT 
    origin, month,
    COUNT(*) AS n_dep_flights,
    AVG(distance) AS avg_distance,
    AVG(arr_delay > 20) AS prop_late_over20
  FROM flights 
  WHERE year = 2017
  GROUP BY origin, month) smry
INNER JOIN airports AS a
  ON (smry.origin = a.faa);
Displaying records 1 - 10
name faa month n_dep_flights avg_distance prop_late_over20
Lehigh Valley Intl ABE 3 167 560.6766 0.1617
Lehigh Valley Intl ABE 4 139 586.3525 0.1367
Lehigh Valley Intl ABE 5 160 574.4938 0.1125
Lehigh Valley Intl ABE 6 134 572.2313 0.1119
Lehigh Valley Intl ABE 7 192 573.5104 0.1354
Lehigh Valley Intl ABE 8 180 562.2167 0.1000
Lehigh Valley Intl ABE 9 206 606.0874 0.0777
Lehigh Valley Intl ABE 10 253 593.4704 0.1344
Lehigh Valley Intl ABE 11 220 585.3227 0.0909
Lehigh Valley Intl ABE 1 191 579.8796 0.1466
airports_smry <- tbl(con_air, "flights") %>%
  filter(year == 2017) %>%
  group_by(origin, month) %>%
  summarize(n_dep_flights = n(),
            avg_distance = mean(distance),
            prop_late_over20 = mean(arr_delay > 20)) %>%
  inner_join(tbl(con_air, "airports") %>%
               select(name, faa),
             by = c("origin" = "faa"))

airports_df <- airports_smry %>% 
  collect()

(airports_df <- airports_df %>%
    rename(faa = origin) %>%
    relocate(name, faa))
dbDisconnect(con_air)
## [1] TRUE
  • With the dataset you wrote out, create a graph that helps illustrate the “worst” airports in terms of late arrivals. You have some freedom in how you define worst and you may want to consider some of the other variables you computed. Do some theming to make your graph look glamorous (those of you who weren’t in my intro data science class this year may want to watch Will Chase’s Glamour of Graphics talk for inspiration).
airports_df %>%
  group_by(name, faa) %>%
  summarize(
    lower = quantile(prop_late_over20, .25),
    upper = quantile(prop_late_over20, .75),
    med = median(prop_late_over20)) %>%
  mutate(
    # this airport's name contains \ characters so I'm removing them to display in the graph
    name = gsub("\\\\+", "", name)
    ) %>%
  filter(med >= .25) %>% 
  ggplot(aes(y = med,
             x = fct_reorder(name, med, .desc = TRUE))) +
  geom_pointrange(aes(ymin = lower, ymax = upper), size = .8, col = "#6388b4") +
  geom_point(col = "#8cc2ca", size = 2.8) +
  coord_flip() +
  labs(title = "Airports with the Highest Proportion of Late Arrivals",
       subtitle = "<span style='color:#6388b4;'>First</span>, <span style='color:#8cc2ca;'>Second</span>, and <span style='color:#6388b4;'>Third</span> Quartiles of Monthly Proportions, 2017") +
  theme(plot.title.position = "plot",
        axis.title = element_blank(),
        axis.text = element_text(size = 8),
        title = element_markdown(size = 12),
        plot.subtitle = element_markdown())

  • Although your graph was truly inspirational, you’ve been requested to “boil it down to a few numbers.” Some people just don’t appreciate all that effort you put in. And, you need to use the already summarized data that you already pulled in from SQL. Create a table with 6 or fewer rows and 3 or fewer columns that summarizes which airport is the “worst” in terms of late arrivals. Be careful with your calculations. You may consider using the kable, kableExtra, or gt packages to make your table look truly spectacular.
airports_df %>%
  group_by(name) %>%
  summarize(
    avg_flights = mean(n_dep_flights),
    avg_prop_late = mean(prop_late_over20)
  ) %>%
  arrange(desc(avg_prop_late)) %>%
  head(6) %>%
  mutate(
  name = gsub("\\\\+", "", name)
  ) %>%
  kable(
    digits = 3,
    caption = "Airports with the Highest Proportion of Late Arrivals, 2017",
    col.names = c("Airport", "Number of Departing Flights", "Proportion of Late Arrivals"),
    align = c("l", "c", "c")
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover")
  ) %>%
  row_spec(1, bold = T) %>%
  add_footnote("Numbers are monthly averages.", notation = "none")
Airports with the Highest Proportion of Late Arrivals, 2017
Airport Number of Departing Flights Proportion of Late Arrivals
Sioux Gateway Col Bud Day Fld 14.000 0.630
East Texas Rgnl 8.000 0.500
St. Augustine Airport 13.200 0.364
Southwest Oregon Regional Airport 25.250 0.354
Nantucket Mem 110.833 0.349
Martha’s Vineyard 40.167 0.341
Numbers are monthly averages.
  1. Come up with your own interesting question that data in the airlines database can help you answer. Write a SQL query and equivalent R code chunk to extract the data you need and create an elegant graph to help answer the question. Be sure to write down the question so it is clear.

What are the busiest airports in terms of departures and for each of them, what is its most popular destination?

con_air <- dbConnect_scidb("airlines")
SELECT
  origin_faa,
  origin_name,
  dest_faa,
  dest_name,
  n_flights
FROM (
  SELECT
    origin, 
    dest,
    COUNT(*) AS n_flights
  FROM flights
  GROUP BY origin, dest) smry
  INNER JOIN (SELECT faa AS origin_faa, name AS origin_name FROM airports) AS a
    ON (smry.origin = a.origin_faa)
  INNER JOIN (SELECT faa AS dest_faa, name AS dest_name FROM airports) AS b
    ON (smry.dest = b.dest_faa)
Displaying records 1 - 10
origin_faa origin_name dest_faa dest_name n_flights
ABE Lehigh Valley Intl ATL Hartsfield Jackson Atlanta Intl 6416
ABE Lehigh Valley Intl AVP Wilkes Barre Scranton Intl 1
ABE Lehigh Valley Intl CLE Cleveland Hopkins Intl 34
ABE Lehigh Valley Intl CLT Charlotte Douglas Intl 1414
ABE Lehigh Valley Intl DTW Detroit Metro Wayne Co 5652
ABE Lehigh Valley Intl EWR Newark Liberty Intl 1
ABE Lehigh Valley Intl FLL Fort Lauderdale Hollywood Intl 153
ABE Lehigh Valley Intl IAD Washington Dulles Intl 278
ABE Lehigh Valley Intl MCO Orlando Intl 1292
ABE Lehigh Valley Intl ORD Chicago Ohare Intl 7391
airports_connect <- tbl(con_air, "flights") %>%
  group_by(origin, dest) %>%
  summarize(n_flights = n()) %>%
  inner_join(tbl(con_air, "airports") %>%
               rename(origin_name = name) %>%
               select(origin_name, faa),
             by = c("origin" = "faa")) %>%
  inner_join(tbl(con_air, "airports") %>%
               rename(dest_name = name) %>%
               select(dest_name, faa),
             by = c("dest" = "faa")) %>%
  select(origin, origin_name, dest, dest_name, n_flights)

(airports_connections <- airports_connect %>%
    collect())
dbDisconnect(con_air)
## [1] TRUE
airports_connections %>%
  group_by(origin, origin_name) %>%
  summarize(max_flights = max(n_flights),
            rem_flights = sum(n_flights) - max_flights) %>%
  inner_join(airports_connections %>%
               select(origin, dest, dest_name, n_flights), 
             by = c("origin" = "origin", "max_flights" = "n_flights")) %>%
  # slice_max(max_flights, n = 10) ## this doesn't work for some reason...
  arrange(desc(max_flights + rem_flights)) %>%
  head(10) %>%
  pivot_longer(ends_with("flights"), values_to = "n_flights", names_to = "type") %>%
  mutate(dest = ifelse(type == "max_flights", dest, "Rem.")) %>%
  mutate(dest = fct_relevel(dest, "Rem.")) %>%
  group_by(origin) %>%
  mutate(tot_flights = sum(n_flights)) %>%
  filter(dest != "Rem") %>%
  ggplot(aes(x = n_flights, y = fct_reorder(origin_name, n_flights), fill = dest)) +
  geom_col(position = position_stack(reverse = TRUE), col = "white") +
  geom_text(data = . %>% 
              filter(dest != "Rem.") %>%
              ungroup() %>%
              distinct(dest_name,
                       .keep_all = TRUE), 
            aes(label = dest_name, x = tot_flights), 
            size = 3, 
            hjust = -.1) +
  scale_fill_tableau(palette = "Superfishel Stone", direction = 1) + 
  labs(title = "Top 10 Busiest Airports in Terms of Departing Flights",
       subtitle = "Number of Total Departures to <span style='color:#6388b4;'>Other Airports</span> and to Most Popular Destination") +
  theme(axis.title = element_blank(),
        axis.text.x = element_text(vjust = -.5),
        plot.title.position = "plot",
        plot.subtitle = element_markdown(),
        legend.position = "none",
        panel.grid = element_blank()) +
  xlim(0, 3500000)

Function Friday

If you need to revisit the material, it is posted on the moodle page. I’ve tried to add all the necessary libraries to the top, but I may have missed something.

geom_sf() tasks:

Using the example from class that we presented as a baseline (or your own if you really want to be ambitious), try to add the following components to the map of the contiguous United States:

  1. Change the color scheme of the map from the default blue (one option could be viridis).
  2. Add a dot (or any symbol you want) to the centroid of each state.
  3. Add a layer onto the map with the counties.
  4. Change the coordinates of the map to zoom in on your favorite state.

Hint: https://www.r-spatial.org/r/2018/10/25/ggplot2-sf-2.html is a useful reference for some of the questions

states <- st_as_sf(maps::map("state", 
                             plot = FALSE, 
                             fill = TRUE))
states <- states %>%
  mutate(area = as.numeric(st_area(states)))

# create centroid
states <- cbind(states, st_coordinates(st_centroid(states)))

# join state names to state abbreviations for mapping
data(state)
states <- states %>%
  inner_join(data.frame(name = tolower(state.name),
                        abb = state.abb),
             by = c("ID" = "name"))

# get counties
counties <- st_as_sf(map("county", plot = FALSE, fill = TRUE))
counties$area <- as.numeric(st_area(counties))
ggplot(data = states) +
  geom_sf(aes(fill = area)) +
  geom_sf(data = counties, fill = NA, color = "darkgrey", size = .1) +
  geom_text(data = states, aes(X, Y, label = abb), size = 3) +
  coord_sf(xlim = c(-127, -63), 
           ylim = c(24, 51), 
           expand = FALSE) +
  theme_map() +
  scale_fill_viridis_c(alpha = .8, direction = -1) +
  labs(fill = "Area") +
  theme(legend.position = c(.85, 0))

ggplot(data = states) +
  geom_sf(aes(fill = area)) +
  geom_sf(data = counties, fill = NA, color = "darkgrey", size = .1) +
  geom_text(data = states %>% filter(abb == "MN"), aes(X, Y, label = abb), size = 5) +
  coord_sf(xlim = c(-98, -89), 
           ylim = c(43, 50), 
           expand = FALSE) +
  theme_map() +
  scale_fill_viridis_c(alpha = .8, direction = -1) +
  labs(fill = "Area") +
  theme(legend.position = c(1.1, 0))

tidytext tasks:

Now you will try using tidytext on a new dataset about Russian Troll tweets.

Read about the data

These are tweets from Twitter handles that are connected to the Internet Research Agency (IRA), a Russian “troll factory.” The majority of these tweets were posted from 2015-2017, but the datasets encompass tweets from February 2012 to May 2018.

Three of the main categories of troll tweet that we will be focusing on are Left Trolls, Right Trolls, and News Feed. Left Trolls usually pretend to be BLM activists, aiming to divide the democratic party (in this context, being pro-Bernie so that votes are taken away from Hillary). Right trolls imitate Trump supporters, and News Feed handles are “local news aggregators,” typically linking to legitimate news.

For our upcoming analyses, some important variables are:

  • author (handle sending the tweet)
    • content (text of the tweet)
    • language (language of the tweet)
    • publish_date (date and time the tweet was sent)

Variable documentation can be found on Github and a more detailed description of the dataset can be found in this fivethirtyeight article.

Because there are 12 datasets containing 2,973,371 tweets sent by 2,848 Twitter handles in total, we will be using three of these datasets (one from a Right troll, one from a Left troll, and one from a News Feed account).



  1. Read in Troll Tweets Dataset - this takes a while. You can cache it so you don’t need to read it in again each time you knit. Be sure to remove the eval=FALSE!!!!
troll_tweets <- read_csv("https://raw.githubusercontent.com/fivethirtyeight/russian-troll-tweets/master/IRAhandle_tweets_12.csv")
  1. Basic Data Cleaning and Exploration
  1. Remove rows where the tweet was in a language other than English
troll_tweets_mod <- troll_tweets %>%
  filter(language == "English")
  1. Report the dimensions of the dataset
dim(troll_tweets_mod)
## [1] 175966     21
  1. Create two or three basic exploratory plots of the data (ex. plot of the different locations from which tweets were posted, plot of the account category of a tweet)
g1 <- troll_tweets_mod %>%
  group_by(region) %>%
  summarize(n = n()) %>%
  drop_na() %>%
  ggplot(aes(x = n,
             y = fct_reorder(region, n, .desc = TRUE))) +
  geom_col(fill = "lightblue") +
  theme(axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        title = element_text(size = 11),
        plot.title.position = "plot",
        panel.grid = element_blank()) +
  scale_x_log10() +
  labs(title = "Number of Tweets by Region")

g2 <- troll_tweets_mod %>%
  group_by(account_category) %>%
  summarize(n = n()) %>%
  ggplot(aes(x = n,
             y = fct_reorder(account_category, n, .desc = TRUE))) +
  geom_col(fill = "lightblue") +
  theme(axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.title.position = "plot",
        title = element_text(size = 11),
        panel.grid = element_blank()) +
  labs(title = "Number of Tweets by Account Category")

grid.arrange(g1, g2, ncol = 2)

  1. Unnest Tokens

We want each row to represent a word from a tweet, rather than an entire tweet. Be sure to remove the eval=FALSE!!!!

(troll_tweets_untoken <- troll_tweets_mod %>%
   unnest_tokens(output = word, input = content))
  1. Remove stopwords. Be sure to remove the eval=FALSE!!!!
# get rid of stopwords (the, and, etc.)
troll_tweets_cleaned <- troll_tweets_untoken %>%
  anti_join(stop_words)

Take a look at the troll_tweets_cleaned dataset. Are there any other words/letters/numbers that we want to eliminate that weren’t taken care of by stop_words? Be sure to remove the eval=FALSE!!!!

troll_tweets_cleaned %>%
  count(word, name = "count") %>%
  arrange(desc(count))
# get rid of http, https, t.co, rt, amp, single number digits, and singular letters
troll_tweets_cleaned <- troll_tweets_cleaned %>%
  filter(!(word %in% c("http", "https", "t.co", "rt", "amp"))) %>%
  filter(nchar(word) > 1)
  1. Look at a subset of the tweets to see how often the top words appear.
troll_tweets_small <- troll_tweets_cleaned %>%
  count(word) %>%
  slice_max(order_by = n, n = 50) # 50 most occurring words

# visualize the number of times the 50 top words appear
ggplot(troll_tweets_small, 
       aes(y = fct_reorder(word,n), x = n)) +
  geom_col() +
  theme(axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.title.position = "plot") +
  labs(title = "Number of Times the Top 50 Most Common Words Appeared")

  1. Sentiment Analysis
  1. Get the sentiments using the “bing” parameter (which classifies words into “positive” or “negative”)
# Sentiments 
sentiments <- get_sentiments("bing")
  1. Report how many positive and negative words there are in the dataset. Are there more positive or negative words, and why do you think this might be?

Be sure to remove the eval=FALSE!!!!

# assign a sentiment to each word that has one associated
troll_tweets_sentiment <- troll_tweets_cleaned %>%
  inner_join(sentiments)

# count the sentiments
troll_tweets_sentiment %>% 
  count(sentiment)

There are more negative words. If the trolls were, well, trolling then they must have tried to be as polarizing as possible and therefore their tweets were loaded with more negative sentiments.

  1. Using the troll_tweets_small dataset, make a wordcloud:
  1. That is sized by the number of times that a word appears in the tweets
# make a wordcloud where the size of the word is based on the number of times the word appears across the tweets
troll_tweets_small %>%
  with(wordcloud(word, n))

  1. That is colored by sentiment (positive or negative)
# make a wordcloud colored by sentiment
troll_tweets_sentiment %>%
  count(word, sentiment, sort = TRUE) %>%
  acast(word ~ sentiment, value.var = "n", fill = 0) %>%
  comparison.cloud(colors = c("red", "green"),
                   max.words = 50)

Are there any words whose categorization as “positive” or “negative” surprised you?

Well aside for the big green “trump” word in the middle of the wordcloud, I’m not really surprised by the results. Technically the word does have a positive meaning, but in the context of things it is kinda funny and sad at the same time.

Projects

Read the project description on the moodle page. Talk to your group members about potential topics.

Task:

Write a short paragraph about ideas you have. If you already have some data sources in mind, you can link to those, but I’m more concerned with you having a topic that you’re interested in investigating right now.

Our group has narrowed down to two broad ideas: K-12 computer science education and mental health resources. We’re interested in the availability and maybe quality of each so the project might lean more towards visualization as opposed to modeling depending on the data we acquire. We’ll also branch out during our individual data search and are still open to ideas if we come across interesting data that are related to CS education or mental health.

“Undoing” bias

Task:

Read this tweet thread by Deb Raji who you may remember from the Coded Bias film. Write a short paragraph that discusses at least one of the misconceptions.

I think the last misconception really drives home the nuance and challenge of addressing and tackling bias in algorithms. First off the problem itself is hard to identify. Racial and gender biases are so ingrained and bleed into many different variables (that pertain to socio-economic status, for example), that even when the algorithms don’t explicitly include race and gender as covariates, such biases will still be present. Although we can test the system on various populations and compare its accuracy and fairness, it would be hard to pinpoint exactly where bias was introduced, let alone address it. As Deb Raji pointed out at the beginning, biases can arise at any point and by any design decision. This not only makes them obscure and hard to detect but also naturally leads to the possibility of introducing other biases when people try to “fix” the issues. Therefore, the challenge of creating and framing inventions is made harder as there is no definitive point where people can stop and conclude that the system is completely reliable or fair. If we did frame them as such, then we’d fall back into the trap of blindly using it without further question or evaluation. Ultimately these systems can never be flawless and as we collect more data and as society progresses, they need to be constantly monitored to ensure that their use and results are still appropriate and applicable to the target population.

LS0tCnRpdGxlOiAnQXNzaWdubWVudCAjMycKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UpCm9wdGlvbnMoc2NpcGVuID0gOTk5KQpgYGAKCmBgYHtyIGxpYnJhcmllcywgbWVzc2FnZT1GQUxTRX0KIyBTRUUgbW9kZWxkYXRhIHBhY2thZ2UgZm9yIG5ldyBkYXRhc2V0cwpsaWJyYXJ5KHRpZHl2ZXJzZSkgICAgICAgICAjIGZvciBncmFwaGluZyBhbmQgZGF0YSBjbGVhbmluZwpsaWJyYXJ5KHRpZHltb2RlbHMpICAgICAgICAjIGZvciBtb2RlbGluZwpsaWJyYXJ5KHN0YWNrcykgICAgICAgICAgICAjIGZvciBzdGFja2luZyBtb2RlbHMKbGlicmFyeShuYW5pYXIpICAgICAgICAgICAgIyBmb3IgZXhhbWluaW5nIG1pc3NpbmcgdmFsdWVzIChOQXMpCmxpYnJhcnkobHVicmlkYXRlKSAgICAgICAgICMgZm9yIGRhdGUgbWFuaXB1bGF0aW9uCmxpYnJhcnkobW9kZXJuZGl2ZSkgICAgICAgICMgZm9yIEtpbmcgQ291bnR5IGhvdXNpbmcgZGF0YQpsaWJyYXJ5KERBTEVYKSAgICAgICAgICAgICAjIGZvciBtb2RlbCBpbnRlcnByZXRhdGlvbiAgCmxpYnJhcnkoREFMRVh0cmEpICAgICAgICAgICMgZm9yIGV4dGVuc2lvbiBvZiBEQUxFWApsaWJyYXJ5KHBhdGNod29yaykgICAgICAgICAjIGZvciBjb21iaW5pbmcgcGxvdHMgbmljZWx5CmxpYnJhcnkoZ3JpZCkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoZ2d0ZXh0KQpsaWJyYXJ5KGRicGx5cikgICAgICAgICAgICAjIGZvciBTUUwgcXVlcnkgImNoZWF0aW5nIiAtIHBhcnQgb2YgdGlkeXZlcnNlIGJ1dCBuZWVkcyB0byBiZSBsb2FkZWQgc2VwYXJhdGVseQpsaWJyYXJ5KG1kc3IpICAgICAgICAgICAgICAjIGZvciBhY2Nlc3Npbmcgc29tZSBkYXRhYmFzZXMgLSBnb2VzIHdpdGggTW9kZXJuIERhdGEgU2NpZW5jZSB3aXRoIFIgdGV4dGJvb2sKbGlicmFyeShSTXlTUUwpICAgICAgICAgICAgIyBmb3IgYWNjZXNzaW5nIE15U1FMIGRhdGFiYXNlcwpsaWJyYXJ5KFJTUUxpdGUpICAgICAgICAgICAjIGZvciBhY2Nlc3NpbmcgU1FMaXRlIGRhdGFiYXNlcwpsaWJyYXJ5KGthYmxlRXh0cmEpCgojbWFwcGluZwpsaWJyYXJ5KG1hcHMpICAgICAgICAgICAgICAjIGZvciBidWlsdC1pbiBtYXBzCmxpYnJhcnkoc2YpICAgICAgICAgICAgICAgICMgZm9yIG1ha2luZyBtYXBzIHVzaW5nIGdlb21fc2YKbGlicmFyeShnZ3RoZW1lcykgICAgICAgICAgIyBMaXNhIGFkZGVkIC0gSSBsaWtlIHRoZW1lX21hcCgpIGZvciBtYXBzIDopCmxpYnJhcnkodmlyaWRpcykKCiN0aWR5dGV4dApsaWJyYXJ5KHRpZHl0ZXh0KSAgICAgICAgICAjIGZvciB0ZXh0IGFuYWx5c2lzLCB0aGUgdGlkeSB3YXkhCmxpYnJhcnkodGV4dGRhdGEpICAgICAgICAgIApsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KHdvcmRjbG91ZCkgICAgICAgICAjIGZvciB3b3JkY2xvdWQKbGlicmFyeShzdG9wd29yZHMpCgp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKSAjIExpc2EncyBmYXZvcml0ZSB0aGVtZQpgYGAKCldoZW4geW91IGZpbmlzaCB0aGUgYXNzaWdubWVudCwgcmVtb3ZlIHRoZSBgI2AgZnJvbSB0aGUgb3B0aW9ucyBjaHVuayBhdCB0aGUgdG9wLCBzbyB0aGF0IG1lc3NhZ2VzIGFuZCB3YXJuaW5ncyBhcmVuJ3QgcHJpbnRlZC4gSWYgeW91IGFyZSBnZXR0aW5nIGVycm9ycyBpbiB5b3VyIGNvZGUsIGFkZCBgZXJyb3IgPSBUUlVFYCBzbyB0aGF0IHRoZSBmaWxlIGtuaXRzLiBJIHdvdWxkIHJlY29tbWVuZCBub3QgcmVtb3ZpbmcgdGhlIGAjYCB1bnRpbCB5b3UgYXJlIGNvbXBsZXRlbHkgZmluaXNoZWQuCgojIyBQdXQgaXQgb24gR2l0SHViISAgICAgICAgCgpGcm9tIG5vdyBvbiwgR2l0SHViIHNob3VsZCBiZSBwYXJ0IG9mIHlvdXIgcm91dGluZSB3aGVuIGRvaW5nIGFzc2lnbm1lbnRzLiBJIHJlY29tbWVuZCBtYWtpbmcgaXQgcGFydCBvZiB5b3VyIHByb2Nlc3MgYW55dGltZSB5b3UgYXJlIHdvcmtpbmcgaW4gUiwgYnV0IEknbGwgbWFrZSB5b3Ugc2hvdyBpdCdzIHBhcnQgb2YgeW91ciBwcm9jZXNzIGZvciBhc3NpZ25tZW50cy4KCioqVGFzayoqOiBXaGVuIHlvdSBhcmUgZmluaXNoZWQgd2l0aCB0aGUgYXNzaWdubWVudCwgcG9zdCBhIGxpbmsgYmVsb3cgdG8gdGhlIEdpdEh1YiByZXBvIGZvciB0aGUgYXNzaWdubWVudC4gSWYgeW91IHdhbnQgdG8gcG9zdCBpdCB0byB5b3VyIHBlcnNvbmFsIHdlYnNpdGUsIHRoYXQncyBvayAobm90IHJlcXVpcmVkKS4gTWFrZSBzdXJlIHRoZSBsaW5rIGdvZXMgdG8gYSBzcG90IGluIHRoZSByZXBvIHdoZXJlIEkgY2FuIGVhc2lseSBmaW5kIHRoaXMgYXNzaWdubWVudC4gRm9yIGV4YW1wbGUsIGlmIHlvdSBoYXZlIGEgd2Vic2l0ZSB3aXRoIGEgYmxvZyBhbmQgcG9zdCB0aGUgYXNzaWdubWVudCBhcyBhIGJsb2cgcG9zdCwgbGluayB0byB0aGUgcG9zdCdzIGZvbGRlciBpbiB0aGUgcmVwby4gQXMgYW4gZXhhbXBsZSwgSSd2ZSBsaW5rZWQgdG8gbXkgR2l0SHViIHN0YWNraW5nIG1hdGVyaWFsIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vbGxlbmR3YXkvYWRzX3dlYnNpdGUvdHJlZS9tYXN0ZXIvX3Bvc3RzLzIwMjEtMDMtMjItc3RhY2tpbmcpLgoKaHR0cHM6Ly9naXRodWIuY29tL3RoeXRuZy9zdGF0NDk0LWFzc2lnbm1lbnQzCgojIyBMb2NhbCBJbnRlcnByZXRhYmxlIE1hY2hpbmUgTGVhcm5pbmcKCllvdSBhcmUgZ29pbmcgdG8gdXNlIHRoZSBLaW5nIENvdW50eSBob3VzZSBkYXRhIGFuZCB0aGUgc2FtZSByYW5kb20gZm9yZXN0IG1vZGVsIHRvIHByZWRpY3QgYGxvZ19wcmljZWAgdGhhdCBJIHVzZWQgaW4gdGhlIFt0dXRvcmlhbF0oaHR0cHM6Ly9hZHZhbmNlZC1kcy1pbi1yLm5ldGxpZnkuYXBwL3Bvc3RzLzIwMjEtMDMtMzEtaW1sbG9jYWwvKS4KCmBgYHtyIGRhdGF9CiMgTG9hZCBpbiB0aGUgZGF0YQpkYXRhKCJob3VzZV9wcmljZXMiKQoKIyBDcmVhdGUgbG9nX3ByaWNlIGFuZCBkcm9wIHByaWNlIHZhcmlhYmxlCmhvdXNlX3ByaWNlcyA8LSBob3VzZV9wcmljZXMgJT4lIAogIG11dGF0ZShsb2dfcHJpY2UgPSBsb2cocHJpY2UsIGJhc2UgPSAxMCkpICU+JSAKICAjIG1ha2UgYWxsIGludGVnZXJzIG51bWVyaWMgLi4uIGZpeGVzIHByZWRpY3Rpb24gcHJvYmxlbQogIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMuaW50ZWdlciksIGFzLm51bWVyaWMpKSAlPiUgCiAgc2VsZWN0KC1wcmljZSkKYGBgCgpgYGB7ciByZi1tb2RlbCwgY2FjaGU9VFJVRX0Kc2V0LnNlZWQoMzI3KSAjZm9yIHJlcHJvZHVjaWJpbGl0eQoKIyBSYW5kb21seSBhc3NpZ25zIDc1JSBvZiB0aGUgZGF0YSB0byB0cmFpbmluZy4KaG91c2Vfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChob3VzZV9wcmljZXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb3AgPSAuNzUpCmhvdXNlX3RyYWluaW5nIDwtIHRyYWluaW5nKGhvdXNlX3NwbGl0KQpob3VzZV90ZXN0aW5nIDwtIHRlc3RpbmcoaG91c2Vfc3BsaXQpCgojIFNldCB1cCByZWNpcGUgYW5kIHRyYW5zZm9ybWF0aW9uIHN0ZXBzIGFuZCByb2xlcwpyYW5nZXJfcmVjaXBlIDwtIAogIHJlY2lwZShmb3JtdWxhID0gbG9nX3ByaWNlIH4gLiwgCiAgICAgICAgIGRhdGEgPSBob3VzZV90cmFpbmluZykgJT4lIAogIHN0ZXBfZGF0ZShkYXRlLCAKICAgICAgICAgICAgZmVhdHVyZXMgPSAibW9udGgiKSAlPiUgCiAgIyBNYWtlIHRoZXNlIGV2YWx1YXRpdmUgdmFyaWFibGVzLCBub3QgaW5jbHVkZWQgaW4gbW9kZWxpbmcKICB1cGRhdGVfcm9sZShhbGxfb2YoYygiaWQiLAogICAgICAgICAgICAgICAgICAgICAgICJkYXRlIikpLAogICAgICAgICAgICAgIG5ld19yb2xlID0gImV2YWx1YXRpdmUiKQoKIyBEZWZpbmUgbW9kZWwKcmFuZ2VyX3NwZWMgPC0gCiAgcmFuZF9mb3Jlc3QobXRyeSA9IDYsIAogICAgICAgICAgICAgIG1pbl9uID0gMTAsIAogICAgICAgICAgICAgIHRyZWVzID0gMjAwKSAlPiUgCiAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKSAlPiUgCiAgc2V0X2VuZ2luZSgicmFuZ2VyIikKCiMgQ3JlYXRlIHdvcmtmbG93CnJhbmdlcl93b3JrZmxvdyA8LSAKICB3b3JrZmxvdygpICU+JSAKICBhZGRfcmVjaXBlKHJhbmdlcl9yZWNpcGUpICU+JSAKICBhZGRfbW9kZWwocmFuZ2VyX3NwZWMpIAoKIyBGaXQgdGhlIG1vZGVsCnNldC5zZWVkKDcxMikgIyBmb3IgcmVwcm9kdWNpYmlsaXR5IC0gcmFuZG9tIHNhbXBsaW5nIGluIHJhbmRvbSBmb3Jlc3QgY2hvb3NpbmcgbnVtYmVyIG9mIHZhcmlhYmxlcwpyYW5nZXJfZml0IDwtIHJhbmdlcl93b3JrZmxvdyAlPiUgCiAgZml0KGhvdXNlX3RyYWluaW5nKQoKIyBDcmVhdGUgYW4gZXhwbGFpbmVyCnJmX2V4cGxhaW4gPC0gCiAgZXhwbGFpbl90aWR5bW9kZWxzKAogICAgbW9kZWwgPSByYW5nZXJfZml0LAogICAgZGF0YSA9IGhvdXNlX3RyYWluaW5nICU+JSBzZWxlY3QoLWxvZ19wcmljZSksIAogICAgeSA9IGhvdXNlX3RyYWluaW5nICU+JSAgcHVsbChsb2dfcHJpY2UpLAogICAgbGFiZWwgPSAicmYiCiAgKQpgYGAKCioqVGFza3M6KioKCjEuIENob29zZSAzIG5ldyBvYnNlcnZhdGlvbnMgYW5kIGRvIHRoZSBmb2xsb3dpbmcgZm9yIGVhY2ggb2JzZXJ2YXRpb246ICAKCmBgYHtyfQpvYnMgPC0gaG91c2VfdGVzdGluZyAlPiUgc2FtcGxlX24oc2l6ZT0zKQpgYGAKCiAgLSBDb25zdHJ1Y3QgYSBicmVhay1kb3duIHBsb3QgdXNpbmcgdGhlIGRlZmF1bHQgb3JkZXJpbmcuIEludGVycHJldCB0aGUgcmVzdWx0aW5nIGdyYXBoLiBXaGljaCB2YXJpYWJsZXMgY29udHJpYnV0ZSBtb3N0IHRvIGVhY2ggb2JzZXJ2YXRpb24ncyBwcmVkaWN0aW9uPyAgCgpgYGB7ciBicmVha2Rvd24sIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD02LCBmaWcuYWxpZ249ImNlbnRlciIsIGNhY2hlPVRSVUV9CmZvciAoaSBpbiAxOjMpIHsKICBuZXdfb2JzIDwtIG9icyAlPiUgc2xpY2UoaSkKICAjIFB1bGxzIHRvZ2V0aGVyIHRoZSBkYXRhIG5lZWRlZCBmb3IgdGhlIGJyZWFrLWRvd24gcGxvdAogIHBwX3JmIDwtIHByZWRpY3RfcGFydHMoZXhwbGFpbmVyID0gcmZfZXhwbGFpbiwKICAgICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG5ld19vYnMsCiAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gImJyZWFrX2Rvd24iKQogIAogICMgQnJlYWstZG93biBwbG90CiAgYm51bSA8LSBwYXN0ZSgiYiIsIGksIHNlcD0iIikKICBiIDwtIHBsb3QocHBfcmYpCiAgYXNzaWduKGJudW0sIGIpCn0KCmdyaWQuYXJyYW5nZShiMSwgYjIsIGIzLCBuY29sPTMpCmBgYAoKKipgbGF0YCBhbmQgYHNxZnRfbGl2aW5nYCBjb250cmlidXRlIG1vc3QgdG8gZWFjaCBvYnNlcnZhdGlvbidzIHByZWRpY3Rpb24uIEZvciB0aGUgZmlyc3QgYW5kIHNlY29uZCBvYnNlcnZhdGlvbnMsIGBncmFkZT02YCAob3IgNyBzaW5jZSB0aGUgZnVuY3Rpb24gc29tZWhvdyB0cmFuc2Zvcm1lZCB0aGlzIHZhbHVlIGR1cmluZyB0aGUgcHJvY2VzcykgaXMgdGhlIHRoaXJkIG1vc3QgImltcG9ydGFudCIgY292YXJpYXRlcy4gRm9yIHRoZSB0aGlyZCBvbmUsIGl0IGlzIGBzcWZ0X2xpdmluZzE1YC4qKgoKICAtIENvbnN0cnVjdCBhIFNIQVAgZ3JhcGggYW5kIGludGVycHJldCBpdC4gRG9lcyBpdCB0ZWxsIGEgc2ltaWxhciBzdG9yeSB0byB0aGUgYnJlYWstZG93biBwbG90PyAgCiAgCmBgYHtyIHNoYXAsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD02LCBmaWcuYWxpZ249ImNlbnRlciIsIGNhY2hlPVRSVUV9CmZvciAoaSBpbiAxOjMpIHsKICBuZXdfb2JzIDwtIG9icyAlPiUgc2xpY2UoaSkKICByZl9zaGFwIDwtcHJlZGljdF9wYXJ0cyhleHBsYWluZXIgPSByZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG5ld19vYnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJzaGFwIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBCID0gMTApIAogIHNudW0gPC0gcGFzdGUoInMiLCBpLCBzZXA9IiIpCiAgcyA8LSBwbG90KHJmX3NoYXApCiAgYXNzaWduKHNudW0sIHMpCn0KCmdyaWQuYXJyYW5nZShzMSwgczIsIHMzLCBuY29sPTMpCmBgYAoKKipUaGUgcGxvdHMgZG8gc2VlbSB0byBhZ3JlZS4gT25jZSBhZ2FpbiwgYGxhdGAgYW5kIGBzcWZ0X2xpdmluZ2AgY29tZSBvdXQgYXMgdGhlIHZhcmlhYmxlcyB3aXRoIHRoZSBiaWdnZXN0IGNvbnRyaWJ1dGlvbiBmb3IgYWxsIHRocmVlIG9ic2VydmF0aW9ucy4gVGhlIHBsb3RzIGNvcnJlY3RseSBkaXNwbGF5IGBncmFkZT03YCBhcyB0aGUgdGhpcmQgbW9zdCBpbXBvcnRhbnQgY292YXJpYXRlIGZvciB0aGUgZmlyc3QgdHdvLiBGb3IgdGhlIHRoaXJkIG9ic2VydmF0aW9uLCBgbG9uZ2AgYW5kIGBzcWZ0X2xpdmluZzE1YCBhcmUgdGhlIHRoaXJkIGFuZCBmb3VydGggbW9zdCBpbXBvcnRhbnQgcmVzcGVjdGl2ZWx5LCBidXQgaW4gdGhlIGJyZWFrZG93biBwbG90IHRoZWlyIG9yZGVyIG9mIGltcG9ydGFuY2UgaXMgcmV2ZXJzZWQuKioKCiAgLSBDb25zdHJ1Y3QgYSBMSU1FIGdyYXBoIChmb2xsb3cgbXkgY29kZSBjYXJlZnVsbHkpLiBIb3cgY2xvc2UgaXMgZWFjaCBvcmlnaW5hbCBwcmVkaWN0aW9uIHRvIHRoZSBwcmVkaWN0aW9uIGZyb20gdGhlIGxvY2FsIG1vZGVsPyBJbnRlcnByZXQgdGhlIHJlc3VsdC4gWW91IGNhbiBhbHNvIHRyeSB1c2luZyBmZXdlciBvciBtb3JlIHZhcmlhYmxlcyBpbiB0aGUgbG9jYWwgbW9kZWwgdGhhbiBJIHVzZWQgaW4gdGhlIGV4YW1wbGUuICAKCmBgYHtyIGxpbWUsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD02LCBmaWcuYWxpZ249ImNlbnRlciIsIGNhY2hlPVRSVUV9CnNldC5zZWVkKDY0NikKCnByZWRzIDwtIGRhdGEuZnJhbWUobW9kZWxfcjIgPSBkb3VibGUoKSwgbW9kZWxfcHJlZGljdGlvbiA9IGRvdWJsZSgpLCBwcmVkaWN0aW9uID0gZG91YmxlKCkpCgpmb3IgKGkgaW4gMTozKSB7CiAgbmV3X29icyA8LSBvYnMgJT4lIHNsaWNlKGkpCiAgCiAgbW9kZWxfdHlwZS5kYWxleF9leHBsYWluZXIgPC0gREFMRVh0cmE6Om1vZGVsX3R5cGUuZGFsZXhfZXhwbGFpbmVyCiAgcHJlZGljdF9tb2RlbC5kYWxleF9leHBsYWluZXIgPC0gREFMRVh0cmE6OnByZWRpY3RfbW9kZWwuZGFsZXhfZXhwbGFpbmVyCiAgCiAgbGltZV9yZiA8LSBwcmVkaWN0X3N1cnJvZ2F0ZShleHBsYWluZXIgPSByZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3X29ic2VydmF0aW9uID0gbmV3X29icyAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KC1sb2dfcHJpY2UpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fZmVhdHVyZXMgPSA1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9wZXJtdXRhdGlvbnMgPSAxMDAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJsaW1lIikKICBwcmVkcyA8LSByYmluZChwcmVkcywgbGltZV9yZiAlPiUgCiAgICAgICAgICAgICAgICAgICBzZWxlY3QobW9kZWxfcjIsIG1vZGVsX3ByZWRpY3Rpb24sIHByZWRpY3Rpb24pICU+JSAKICAgICAgICAgICAgICAgICAgIGRpc3RpbmN0KCkpCiAgbG51bSA8LSBwYXN0ZSgibCIsIGksIHNlcD0iIikKICBsIDwtIHBsb3QobGltZV9yZikgKwogICAgbGFicyh4ID0gIlZhcmlhYmxlIikKICBhc3NpZ24obG51bSwgbCkKfQpgYGAKCioqVGhlIGxvY2FsIHByZWRpY3Rpb25zIGFyZSBxdWl0ZSBjbG9zZSB0byB0aGUgb3JpZ2luYWxzLiBUaGUgcHJlZGljdGlvbnMgYXJlIHZlcnkgY2xvc2UgZm9yIG9ic2VydmF0aW9uIDEsIGFuZCB0aGV5IGRpZmZlciBhYm91dCAuMSBhbmQgLjIgdW5pdHMgZm9yIHRoZSBzZWNvbmQgYW5kIHRoaXJkIG9ic2VydmF0aW9uIHJlc3BlY3RpdmVseS4gVGhlIExJTUUgcGxvdHMgZGlzcGxheSBhIHNpbWlsYXIgc2V0IG9mIHZhcmlhYmxlcyB0aGF0IGFyZSBjb25zaWRlcmVkIG1vc3QgaW1wb3J0YW50IGZvciBlYWNoIG9ic2VydmF0aW9uIHRvIHRoZSBvbmVzIHdlIHNhdyBmcm9tIHRoZSBicmVhay1kb3duIGFuZCBTSEFQIHBsb3RzLiBFYWNoIHByZWRpY3RvciBoYXMgYSBkaWZmZXJlbnQgd2VpZ2h0IGZvciBlYWNoIG9ic2VydmF0aW9uLCBtZWFuaW5nIHRoYXQgaXQgY2FuIGhhdmUgYSBwb3NpdGl2ZSBvciBuZWdhdGl2ZSByZWxhdGlvbnNoaXAgd2l0aCB0aGUgcmVzcG9uc2UgZGVwZW5kaW5nIG9uIHRoZSBvYnNlcnZhdGlvbi4qKgoKMi4gRGVzY3JpYmUgaG93IHlvdSB3b3VsZCB1c2UgdGhlIGludGVycHJldGFibGUgbWFjaGluZSBsZWFybmluZyB0b29scyB3ZSd2ZSBsZWFybmVkIChib3RoIGxvY2FsIGFuZCBnbG9iYWwpIGluIGZ1dHVyZSBtYWNoaW5lIGxlYXJuaW5nIHByb2plY3RzPyBIb3cgZG9lcyBlYWNoIG9mIHRoZW0gaGVscCB5b3U/CgoqKlRoZXNlIHRvb2xzIGFyZSBleGNlbGxlbnQgZm9yIHNob3dpbmcgaG93IHByZWRpY3RvcnMgY29udHJpYnV0ZSB0byB0aGUgbW9kZWwuIEl0IGlzIHZlcnkgbGlrZWx5IHRoYXQgSSB3aWxsIGZpdCBtb2RlbHMgdGhhdCBhcmUgbW9yZSBjb21wbGV4IHRoYW4gcmVndWxhciBsaW5lYXIgb3IgbG9naXN0aWMgcmVncmVzc2lvbiBhbmQgaW4gbXkgZXhwZXJpZW5jZSB0aGVpciBpbnRlcnByZXRhdGlvbiBpcyByZWFsbHkgdGVjaG5pY2FsIGFuZCBub3QgaW50dWl0aXZlIGF0IGFsbC4gV2hhdCBvZnRlbiBoYXBwZW5zIGlzIHRoYXQgSSB3b3VsZCBoYXZlIGEgbW9kZWwgYW5kIHNvbWUgbWVhc3VyZSB0byBnYXVnZSB0aGUgcHJlZGljdG9ycycgY29udHJpYnV0aW9uIChpLmUuIGNvZWZmaWNpZW50IHZhbHVlcywgdmFyaWFibGUgaW1wb3J0YW5jZSwgZXRjLiksIGJ1dCBJIHdvdWxkIGhhdmUgYSBkaWZmaWN1bHQgdGltZSB0cmFuc2xhdGluZyB0aGF0IHRvIHJlc3VsdHMgdGhhdCBJIGNvdWxkIHVuZGVyc3RhbmQsIGxldCBhbG9uZSBjb21tdW5pY2F0ZSB0byBhIGJyb2FkZXIgYXVkaWVuY2UuIEdsb2JhbCBpbnRlcnByZXRhdGlvbiB0b29scyBtYWtlIGl0IGVhc2llciB0byBzZWUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSByZXNwb25zZSBhbmQgYSBnaXZlbiBjb3ZhcmlhdGUgYW5kIGhvdyBjaGFuZ2luZyB0aGUgbGF0dGVyIHdvdWxkIGRpcmVjdGx5IGFmZmVjdCB0aGUgdmFsdWUgb2YgdGhlIGZvcm1lci4gV2l0aCBsb2NhbCBpbnRlcnByZXRhdGlvbiB0b29scywgSSBjb3VsZCB2aXN1YWxpemUgdGhlIGNvbnRyaWJ1dGlvbiBvZiBlYWNoIHZhcmlhYmxlIHRvIGFuIGluZGl2aWR1YWwgb2JzZXJ2YXRpb24uIFVsdGltYXRlbHksIHRoZXNlIHRvb2xzIHdvdWxkIGhlbHAgbWUgdW5kZXJzdGFuZCBhIG1vZGVsIGFuZCBpdHMgY29tcG9uZW50cyBiZXR0ZXIgYW5kIHByb3ZpZGUgbW9yZSBpbnR1aXRpdmUgYW5kIGFwcHJvYWNoYWJsZSBpbnRlcnByZXRhdGlvbnMuKioKCiMjIFNRTAoKWW91IHdpbGwgdXNlIHRoZSBgYWlybGluZXNgIGRhdGEgZnJvbSB0aGUgU1FMIGRhdGFiYXNlIHRoYXQgSSB1c2VkIGluIHRoZSBleGFtcGxlIGluIHRoZSBbdHV0b3JpYWxdKGh0dHBzOi8vYWR2YW5jZWQtZHMtaW4tci5uZXRsaWZ5LmFwcC9wb3N0cy8yMDIxLTAzLTI5LXNxbGluci8pLiBCZSBzdXJlIHRvIGluY2x1ZGUgdGhlIGNodW5rIHRvIGNvbm5lY3QgdG8gdGhlIGRhdGFiYXNlIGhlcmUuIEFuZCwgd2hlbiB5b3UgYXJlIGZpbmlzaGVkLCBkaXNjb25uZWN0LiBZb3UgbWF5IG5lZWQgdG8gcmVjb25uZWN0IHRocm91Z2hvdXQgYXMgaXQgdGltZXMgb3V0IGFmdGVyIGEgd2hpbGUuCgpgYGB7cn0KY29uX2FpciA8LSBkYkNvbm5lY3Rfc2NpZGIoImFpcmxpbmVzIikKYGBgCgoqKlRhc2tzKio6CgoxLiBDcmVhdGUgYSBTUUwgY2h1bmsgYW5kIGFuIGVxdWl2YWxlbnQgUiBjb2RlIGNodW5rIHRoYXQgZG9lcyB0aGUgZm9sbG93aW5nOiBmb3IgZWFjaCBhaXJwb3J0ICh3aXRoIGl0cyBuYW1lLCBub3QgY29kZSksIHllYXIsIGFuZCBtb250aCBmaW5kIHRoZSB0b3RhbCBudW1iZXIgb2YgZGVwYXJ0aW5nIGZsaWdodHMsIHRoZSBkaXN0aW5jdCBkZXN0aW5hdGlvbnMgdG8gd2hpY2ggdGhleSBmbGV3LCB0aGUgYXZlcmFnZSBkaXN0YW5jZSBvZiB0aGUgZmxpZ2h0LCBhbmQgdGhlIHByb3BvcnRpb24gb2YgZmxpZ2h0cyB0aGF0IGFycml2ZWQgbW9yZSB0aGFuIDIwIG1pbnV0ZXMgbGF0ZS4gSW4gdGhlIFIgY29kZSBjaHVuaywgd3JpdGUgdGhpcyBvdXQgdG8gYSBkYXRhc2V0LiAoSElOVDogMS4gc3RhcnQgc21hbGwhIDIuIHlvdSBtYXkgd2FudCB0byBkbyB0aGUgUiBwYXJ0IGZpcnN0IGFuZCB1c2UgaXQgdG8gImNoZWF0IiBpbnRvIHRoZSBTUUwgY29kZSkuICAKCmBgYHtzcWwgY29ubmVjdGlvbj1jb25fYWlyfQpTRUxFQ1QKICBuYW1lLAogIGZhYSwKICBtb250aCwgCiAgbl9kZXBfZmxpZ2h0cywKICBhdmdfZGlzdGFuY2UsCiAgcHJvcF9sYXRlX292ZXIyMApGUk9NICgKICBTRUxFQ1QgCiAgICBvcmlnaW4sIG1vbnRoLAogICAgQ09VTlQoKikgQVMgbl9kZXBfZmxpZ2h0cywKICAgIEFWRyhkaXN0YW5jZSkgQVMgYXZnX2Rpc3RhbmNlLAogICAgQVZHKGFycl9kZWxheSA+IDIwKSBBUyBwcm9wX2xhdGVfb3ZlcjIwCiAgRlJPTSBmbGlnaHRzIAogIFdIRVJFIHllYXIgPSAyMDE3CiAgR1JPVVAgQlkgb3JpZ2luLCBtb250aCkgc21yeQpJTk5FUiBKT0lOIGFpcnBvcnRzIEFTIGEKICBPTiAoc21yeS5vcmlnaW4gPSBhLmZhYSk7CmBgYAoKYGBge3J9CmFpcnBvcnRzX3NtcnkgPC0gdGJsKGNvbl9haXIsICJmbGlnaHRzIikgJT4lCiAgZmlsdGVyKHllYXIgPT0gMjAxNykgJT4lCiAgZ3JvdXBfYnkob3JpZ2luLCBtb250aCkgJT4lCiAgc3VtbWFyaXplKG5fZGVwX2ZsaWdodHMgPSBuKCksCiAgICAgICAgICAgIGF2Z19kaXN0YW5jZSA9IG1lYW4oZGlzdGFuY2UpLAogICAgICAgICAgICBwcm9wX2xhdGVfb3ZlcjIwID0gbWVhbihhcnJfZGVsYXkgPiAyMCkpICU+JQogIGlubmVyX2pvaW4odGJsKGNvbl9haXIsICJhaXJwb3J0cyIpICU+JQogICAgICAgICAgICAgICBzZWxlY3QobmFtZSwgZmFhKSwKICAgICAgICAgICAgIGJ5ID0gYygib3JpZ2luIiA9ICJmYWEiKSkKCmFpcnBvcnRzX2RmIDwtIGFpcnBvcnRzX3NtcnkgJT4lIAogIGNvbGxlY3QoKQoKKGFpcnBvcnRzX2RmIDwtIGFpcnBvcnRzX2RmICU+JQogICAgcmVuYW1lKGZhYSA9IG9yaWdpbikgJT4lCiAgICByZWxvY2F0ZShuYW1lLCBmYWEpKQpgYGAKCmBgYHtyfQpkYkRpc2Nvbm5lY3QoY29uX2FpcikKYGBgCgogIC0gV2l0aCB0aGUgZGF0YXNldCB5b3Ugd3JvdGUgb3V0LCBjcmVhdGUgYSBncmFwaCB0aGF0IGhlbHBzIGlsbHVzdHJhdGUgdGhlICJ3b3JzdCIgYWlycG9ydHMgaW4gdGVybXMgb2YgbGF0ZSBhcnJpdmFscy4gWW91IGhhdmUgc29tZSBmcmVlZG9tIGluIGhvdyB5b3UgZGVmaW5lIHdvcnN0IGFuZCB5b3UgbWF5IHdhbnQgdG8gY29uc2lkZXIgc29tZSBvZiB0aGUgb3RoZXIgdmFyaWFibGVzIHlvdSBjb21wdXRlZC4gRG8gc29tZSB0aGVtaW5nIHRvIG1ha2UgeW91ciBncmFwaCBsb29rIGdsYW1vcm91cyAodGhvc2Ugb2YgeW91IHdobyB3ZXJlbid0IGluIG15IGludHJvIGRhdGEgc2NpZW5jZSBjbGFzcyB0aGlzIHllYXIgbWF5IHdhbnQgdG8gd2F0Y2ggV2lsbCBDaGFzZSdzIFtHbGFtb3VyIG9mIEdyYXBoaWNzXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PWg1Y1RhY2FXRTZJKSB0YWxrIGZvciBpbnNwaXJhdGlvbikuICAKICAKYGBge3IgbGF0ZS1mbGlnaHRzLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00LCBmaWcuYWxpZ249ImNlbnRlciJ9CmFpcnBvcnRzX2RmICU+JQogIGdyb3VwX2J5KG5hbWUsIGZhYSkgJT4lCiAgc3VtbWFyaXplKAogICAgbG93ZXIgPSBxdWFudGlsZShwcm9wX2xhdGVfb3ZlcjIwLCAuMjUpLAogICAgdXBwZXIgPSBxdWFudGlsZShwcm9wX2xhdGVfb3ZlcjIwLCAuNzUpLAogICAgbWVkID0gbWVkaWFuKHByb3BfbGF0ZV9vdmVyMjApKSAlPiUKICBtdXRhdGUoCiAgICAjIHRoaXMgYWlycG9ydCdzIG5hbWUgY29udGFpbnMgXCBjaGFyYWN0ZXJzIHNvIEknbSByZW1vdmluZyB0aGVtIHRvIGRpc3BsYXkgaW4gdGhlIGdyYXBoCiAgICBuYW1lID0gZ3N1YigiXFxcXCsiLCAiIiwgbmFtZSkKICAgICkgJT4lCiAgZmlsdGVyKG1lZCA+PSAuMjUpICU+JSAKICBnZ3Bsb3QoYWVzKHkgPSBtZWQsCiAgICAgICAgICAgICB4ID0gZmN0X3Jlb3JkZXIobmFtZSwgbWVkLCAuZGVzYyA9IFRSVUUpKSkgKwogIGdlb21fcG9pbnRyYW5nZShhZXMoeW1pbiA9IGxvd2VyLCB5bWF4ID0gdXBwZXIpLCBzaXplID0gLjgsIGNvbCA9ICIjNjM4OGI0IikgKwogIGdlb21fcG9pbnQoY29sID0gIiM4Y2MyY2EiLCBzaXplID0gMi44KSArCiAgY29vcmRfZmxpcCgpICsKICBsYWJzKHRpdGxlID0gIkFpcnBvcnRzIHdpdGggdGhlIEhpZ2hlc3QgUHJvcG9ydGlvbiBvZiBMYXRlIEFycml2YWxzIiwKICAgICAgIHN1YnRpdGxlID0gIjxzcGFuIHN0eWxlPSdjb2xvcjojNjM4OGI0Oyc+Rmlyc3Q8L3NwYW4+LCA8c3BhbiBzdHlsZT0nY29sb3I6IzhjYzJjYTsnPlNlY29uZDwvc3Bhbj4sIGFuZCA8c3BhbiBzdHlsZT0nY29sb3I6IzYzODhiNDsnPlRoaXJkPC9zcGFuPiBRdWFydGlsZXMgb2YgTW9udGhseSBQcm9wb3J0aW9ucywgMjAxNyIpICsKICB0aGVtZShwbG90LnRpdGxlLnBvc2l0aW9uID0gInBsb3QiLAogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSwKICAgICAgICB0aXRsZSA9IGVsZW1lbnRfbWFya2Rvd24oc2l6ZSA9IDEyKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF9tYXJrZG93bigpKQpgYGAKICAKICAtIEFsdGhvdWdoIHlvdXIgZ3JhcGggd2FzIHRydWx5IGluc3BpcmF0aW9uYWwsIHlvdSd2ZSBiZWVuIHJlcXVlc3RlZCB0byAiYm9pbCBpdCBkb3duIHRvIGEgZmV3IG51bWJlcnMuIiBTb21lIHBlb3BsZSBqdXN0IGRvbid0IGFwcHJlY2lhdGUgYWxsIHRoYXQgZWZmb3J0IHlvdSBwdXQgaW4uIEFuZCwgeW91IG5lZWQgdG8gdXNlIHRoZSBhbHJlYWR5IHN1bW1hcml6ZWQgZGF0YSB0aGF0IHlvdSBhbHJlYWR5IHB1bGxlZCBpbiBmcm9tIFNRTC4gQ3JlYXRlIGEgdGFibGUgd2l0aCA2IG9yIGZld2VyIHJvd3MgYW5kIDMgb3IgZmV3ZXIgY29sdW1ucyB0aGF0IHN1bW1hcml6ZXMgd2hpY2ggYWlycG9ydCBpcyB0aGUgIndvcnN0IiBpbiB0ZXJtcyBvZiBsYXRlIGFycml2YWxzLiBCZSBjYXJlZnVsIHdpdGggeW91ciBjYWxjdWxhdGlvbnMuIFlvdSBtYXkgY29uc2lkZXIgdXNpbmcgdGhlIGBrYWJsZWAsIGBrYWJsZUV4dHJhYCwgb3IgYGd0YCBwYWNrYWdlcyB0byBtYWtlIHlvdXIgdGFibGUgbG9vayB0cnVseSBzcGVjdGFjdWxhci4KICAKYGBge3J9CmFpcnBvcnRzX2RmICU+JQogIGdyb3VwX2J5KG5hbWUpICU+JQogIHN1bW1hcml6ZSgKICAgIGF2Z19mbGlnaHRzID0gbWVhbihuX2RlcF9mbGlnaHRzKSwKICAgIGF2Z19wcm9wX2xhdGUgPSBtZWFuKHByb3BfbGF0ZV9vdmVyMjApCiAgKSAlPiUKICBhcnJhbmdlKGRlc2MoYXZnX3Byb3BfbGF0ZSkpICU+JQogIGhlYWQoNikgJT4lCiAgbXV0YXRlKAogIG5hbWUgPSBnc3ViKCJcXFxcKyIsICIiLCBuYW1lKQogICkgJT4lCiAga2FibGUoCiAgICBkaWdpdHMgPSAzLAogICAgY2FwdGlvbiA9ICJBaXJwb3J0cyB3aXRoIHRoZSBIaWdoZXN0IFByb3BvcnRpb24gb2YgTGF0ZSBBcnJpdmFscywgMjAxNyIsCiAgICBjb2wubmFtZXMgPSBjKCJBaXJwb3J0IiwgIk51bWJlciBvZiBEZXBhcnRpbmcgRmxpZ2h0cyIsICJQcm9wb3J0aW9uIG9mIExhdGUgQXJyaXZhbHMiKSwKICAgIGFsaWduID0gYygibCIsICJjIiwgImMiKQogICkgJT4lCiAga2FibGVfc3R5bGluZygKICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIpCiAgKSAlPiUKICByb3dfc3BlYygxLCBib2xkID0gVCkgJT4lCiAgYWRkX2Zvb3Rub3RlKCJOdW1iZXJzIGFyZSBtb250aGx5IGF2ZXJhZ2VzLiIsIG5vdGF0aW9uID0gIm5vbmUiKQpgYGAKCiAgCjIuIENvbWUgdXAgd2l0aCB5b3VyIG93biBpbnRlcmVzdGluZyBxdWVzdGlvbiB0aGF0IGRhdGEgaW4gdGhlIGFpcmxpbmVzIGRhdGFiYXNlIGNhbiBoZWxwIHlvdSBhbnN3ZXIuIFdyaXRlIGEgU1FMIHF1ZXJ5IGFuZCBlcXVpdmFsZW50IFIgY29kZSBjaHVuayB0byBleHRyYWN0IHRoZSBkYXRhIHlvdSBuZWVkIGFuZCBjcmVhdGUgYW4gZWxlZ2FudCBncmFwaCB0byBoZWxwIGFuc3dlciB0aGUgcXVlc3Rpb24uIEJlIHN1cmUgdG8gd3JpdGUgZG93biB0aGUgcXVlc3Rpb24gc28gaXQgaXMgY2xlYXIuIAoKKipXaGF0IGFyZSB0aGUgYnVzaWVzdCBhaXJwb3J0cyBpbiB0ZXJtcyBvZiBkZXBhcnR1cmVzIGFuZCBmb3IgZWFjaCBvZiB0aGVtLCB3aGF0IGlzIGl0cyBtb3N0IHBvcHVsYXIgZGVzdGluYXRpb24/KioKCmBgYHtyfQpjb25fYWlyIDwtIGRiQ29ubmVjdF9zY2lkYigiYWlybGluZXMiKQpgYGAKCmBgYHtzcWwgY29ubmVjdGlvbj1jb25fYWlyfQpTRUxFQ1QKICBvcmlnaW5fZmFhLAogIG9yaWdpbl9uYW1lLAogIGRlc3RfZmFhLAogIGRlc3RfbmFtZSwKICBuX2ZsaWdodHMKRlJPTSAoCiAgU0VMRUNUCiAgICBvcmlnaW4sIAogICAgZGVzdCwKICAgIENPVU5UKCopIEFTIG5fZmxpZ2h0cwogIEZST00gZmxpZ2h0cwogIEdST1VQIEJZIG9yaWdpbiwgZGVzdCkgc21yeQogIElOTkVSIEpPSU4gKFNFTEVDVCBmYWEgQVMgb3JpZ2luX2ZhYSwgbmFtZSBBUyBvcmlnaW5fbmFtZSBGUk9NIGFpcnBvcnRzKSBBUyBhCiAgICBPTiAoc21yeS5vcmlnaW4gPSBhLm9yaWdpbl9mYWEpCiAgSU5ORVIgSk9JTiAoU0VMRUNUIGZhYSBBUyBkZXN0X2ZhYSwgbmFtZSBBUyBkZXN0X25hbWUgRlJPTSBhaXJwb3J0cykgQVMgYgogICAgT04gKHNtcnkuZGVzdCA9IGIuZGVzdF9mYWEpCmBgYAoKYGBge3J9CmFpcnBvcnRzX2Nvbm5lY3QgPC0gdGJsKGNvbl9haXIsICJmbGlnaHRzIikgJT4lCiAgZ3JvdXBfYnkob3JpZ2luLCBkZXN0KSAlPiUKICBzdW1tYXJpemUobl9mbGlnaHRzID0gbigpKSAlPiUKICBpbm5lcl9qb2luKHRibChjb25fYWlyLCAiYWlycG9ydHMiKSAlPiUKICAgICAgICAgICAgICAgcmVuYW1lKG9yaWdpbl9uYW1lID0gbmFtZSkgJT4lCiAgICAgICAgICAgICAgIHNlbGVjdChvcmlnaW5fbmFtZSwgZmFhKSwKICAgICAgICAgICAgIGJ5ID0gYygib3JpZ2luIiA9ICJmYWEiKSkgJT4lCiAgaW5uZXJfam9pbih0YmwoY29uX2FpciwgImFpcnBvcnRzIikgJT4lCiAgICAgICAgICAgICAgIHJlbmFtZShkZXN0X25hbWUgPSBuYW1lKSAlPiUKICAgICAgICAgICAgICAgc2VsZWN0KGRlc3RfbmFtZSwgZmFhKSwKICAgICAgICAgICAgIGJ5ID0gYygiZGVzdCIgPSAiZmFhIikpICU+JQogIHNlbGVjdChvcmlnaW4sIG9yaWdpbl9uYW1lLCBkZXN0LCBkZXN0X25hbWUsIG5fZmxpZ2h0cykKCihhaXJwb3J0c19jb25uZWN0aW9ucyA8LSBhaXJwb3J0c19jb25uZWN0ICU+JQogICAgY29sbGVjdCgpKQpgYGAKYGBge3J9CmRiRGlzY29ubmVjdChjb25fYWlyKQpgYGAKCmBgYHtyIGFpcnBvcnQtY29ubmVjdGlvbnMsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTQsIGZpZy5hbGlnbj0iY2VudGVyIn0KYWlycG9ydHNfY29ubmVjdGlvbnMgJT4lCiAgZ3JvdXBfYnkob3JpZ2luLCBvcmlnaW5fbmFtZSkgJT4lCiAgc3VtbWFyaXplKG1heF9mbGlnaHRzID0gbWF4KG5fZmxpZ2h0cyksCiAgICAgICAgICAgIHJlbV9mbGlnaHRzID0gc3VtKG5fZmxpZ2h0cykgLSBtYXhfZmxpZ2h0cykgJT4lCiAgaW5uZXJfam9pbihhaXJwb3J0c19jb25uZWN0aW9ucyAlPiUKICAgICAgICAgICAgICAgc2VsZWN0KG9yaWdpbiwgZGVzdCwgZGVzdF9uYW1lLCBuX2ZsaWdodHMpLCAKICAgICAgICAgICAgIGJ5ID0gYygib3JpZ2luIiA9ICJvcmlnaW4iLCAibWF4X2ZsaWdodHMiID0gIm5fZmxpZ2h0cyIpKSAlPiUKICAjIHNsaWNlX21heChtYXhfZmxpZ2h0cywgbiA9IDEwKSAjIyB0aGlzIGRvZXNuJ3Qgd29yayBmb3Igc29tZSByZWFzb24uLi4KICBhcnJhbmdlKGRlc2MobWF4X2ZsaWdodHMgKyByZW1fZmxpZ2h0cykpICU+JQogIGhlYWQoMTApICU+JQogIHBpdm90X2xvbmdlcihlbmRzX3dpdGgoImZsaWdodHMiKSwgdmFsdWVzX3RvID0gIm5fZmxpZ2h0cyIsIG5hbWVzX3RvID0gInR5cGUiKSAlPiUKICBtdXRhdGUoZGVzdCA9IGlmZWxzZSh0eXBlID09ICJtYXhfZmxpZ2h0cyIsIGRlc3QsICJSZW0uIikpICU+JQogIG11dGF0ZShkZXN0ID0gZmN0X3JlbGV2ZWwoZGVzdCwgIlJlbS4iKSkgJT4lCiAgZ3JvdXBfYnkob3JpZ2luKSAlPiUKICBtdXRhdGUodG90X2ZsaWdodHMgPSBzdW0obl9mbGlnaHRzKSkgJT4lCiAgZmlsdGVyKGRlc3QgIT0gIlJlbSIpICU+JQogIGdncGxvdChhZXMoeCA9IG5fZmxpZ2h0cywgeSA9IGZjdF9yZW9yZGVyKG9yaWdpbl9uYW1lLCBuX2ZsaWdodHMpLCBmaWxsID0gZGVzdCkpICsKICBnZW9tX2NvbChwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHJldmVyc2UgPSBUUlVFKSwgY29sID0gIndoaXRlIikgKwogIGdlb21fdGV4dChkYXRhID0gLiAlPiUgCiAgICAgICAgICAgICAgZmlsdGVyKGRlc3QgIT0gIlJlbS4iKSAlPiUKICAgICAgICAgICAgICB1bmdyb3VwKCkgJT4lCiAgICAgICAgICAgICAgZGlzdGluY3QoZGVzdF9uYW1lLAogICAgICAgICAgICAgICAgICAgICAgIC5rZWVwX2FsbCA9IFRSVUUpLCAKICAgICAgICAgICAgYWVzKGxhYmVsID0gZGVzdF9uYW1lLCB4ID0gdG90X2ZsaWdodHMpLCAKICAgICAgICAgICAgc2l6ZSA9IDMsIAogICAgICAgICAgICBoanVzdCA9IC0uMSkgKwogIHNjYWxlX2ZpbGxfdGFibGVhdShwYWxldHRlID0gIlN1cGVyZmlzaGVsIFN0b25lIiwgZGlyZWN0aW9uID0gMSkgKyAKICBsYWJzKHRpdGxlID0gIlRvcCAxMCBCdXNpZXN0IEFpcnBvcnRzIGluIFRlcm1zIG9mIERlcGFydGluZyBGbGlnaHRzIiwKICAgICAgIHN1YnRpdGxlID0gIk51bWJlciBvZiBUb3RhbCBEZXBhcnR1cmVzIHRvIDxzcGFuIHN0eWxlPSdjb2xvcjojNjM4OGI0Oyc+T3RoZXIgQWlycG9ydHM8L3NwYW4+IGFuZCB0byBNb3N0IFBvcHVsYXIgRGVzdGluYXRpb24iKSArCiAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCh2anVzdCA9IC0uNSksCiAgICAgICAgcGxvdC50aXRsZS5wb3NpdGlvbiA9ICJwbG90IiwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF9tYXJrZG93bigpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKSArCiAgeGxpbSgwLCAzNTAwMDAwKQpgYGAKCiMjIEZ1bmN0aW9uIEZyaWRheQoKSWYgeW91IG5lZWQgdG8gcmV2aXNpdCB0aGUgbWF0ZXJpYWwsIGl0IGlzIHBvc3RlZCBvbiB0aGUgbW9vZGxlIHBhZ2UuIEkndmUgdHJpZWQgdG8gYWRkIGFsbCB0aGUgbmVjZXNzYXJ5IGxpYnJhcmllcyB0byB0aGUgdG9wLCBidXQgSSBtYXkgaGF2ZSBtaXNzZWQgc29tZXRoaW5nLgoKKipgZ2VvbV9zZigpYCB0YXNrcyoqOgoKVXNpbmcgdGhlIGV4YW1wbGUgZnJvbSBjbGFzcyB0aGF0IHdlIHByZXNlbnRlZCBhcyBhIGJhc2VsaW5lIChvciB5b3VyIG93biBpZiB5b3UgcmVhbGx5IHdhbnQgdG8gYmUgYW1iaXRpb3VzKSwgdHJ5IHRvIGFkZCB0aGUgZm9sbG93aW5nIGNvbXBvbmVudHMgdG8gdGhlIG1hcCBvZiB0aGUgY29udGlndW91cyBVbml0ZWQgU3RhdGVzOgoKMS4JQ2hhbmdlIHRoZSBjb2xvciBzY2hlbWUgb2YgdGhlIG1hcCBmcm9tIHRoZSBkZWZhdWx0IGJsdWUgKG9uZSBvcHRpb24gY291bGQgYmUgdmlyaWRpcykuCjIuCUFkZCBhIGRvdCAob3IgYW55IHN5bWJvbCB5b3Ugd2FudCkgdG8gdGhlIGNlbnRyb2lkIG9mIGVhY2ggc3RhdGUuCjMuCUFkZCBhIGxheWVyIG9udG8gdGhlIG1hcCB3aXRoIHRoZSBjb3VudGllcy4KNC4JQ2hhbmdlIHRoZSBjb29yZGluYXRlcyBvZiB0aGUgbWFwIHRvIHpvb20gaW4gb24geW91ciBmYXZvcml0ZSBzdGF0ZS4KCkhpbnQ6IGh0dHBzOi8vd3d3LnItc3BhdGlhbC5vcmcvci8yMDE4LzEwLzI1L2dncGxvdDItc2YtMi5odG1sIGlzIGEgdXNlZnVsIHJlZmVyZW5jZSBmb3Igc29tZSBvZiB0aGUgcXVlc3Rpb25zCgpgYGB7cn0Kc3RhdGVzIDwtIHN0X2FzX3NmKG1hcHM6Om1hcCgic3RhdGUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90ID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBUUlVFKSkKc3RhdGVzIDwtIHN0YXRlcyAlPiUKICBtdXRhdGUoYXJlYSA9IGFzLm51bWVyaWMoc3RfYXJlYShzdGF0ZXMpKSkKCiMgY3JlYXRlIGNlbnRyb2lkCnN0YXRlcyA8LSBjYmluZChzdGF0ZXMsIHN0X2Nvb3JkaW5hdGVzKHN0X2NlbnRyb2lkKHN0YXRlcykpKQoKIyBqb2luIHN0YXRlIG5hbWVzIHRvIHN0YXRlIGFiYnJldmlhdGlvbnMgZm9yIG1hcHBpbmcKZGF0YShzdGF0ZSkKc3RhdGVzIDwtIHN0YXRlcyAlPiUKICBpbm5lcl9qb2luKGRhdGEuZnJhbWUobmFtZSA9IHRvbG93ZXIoc3RhdGUubmFtZSksCiAgICAgICAgICAgICAgICAgICAgICAgIGFiYiA9IHN0YXRlLmFiYiksCiAgICAgICAgICAgICBieSA9IGMoIklEIiA9ICJuYW1lIikpCgojIGdldCBjb3VudGllcwpjb3VudGllcyA8LSBzdF9hc19zZihtYXAoImNvdW50eSIsIHBsb3QgPSBGQUxTRSwgZmlsbCA9IFRSVUUpKQpjb3VudGllcyRhcmVhIDwtIGFzLm51bWVyaWMoc3RfYXJlYShjb3VudGllcykpCmBgYAoKYGBge3IgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NCwgZmlnLmFsaWduPSJjZW50ZXIifQpnZ3Bsb3QoZGF0YSA9IHN0YXRlcykgKwogIGdlb21fc2YoYWVzKGZpbGwgPSBhcmVhKSkgKwogIGdlb21fc2YoZGF0YSA9IGNvdW50aWVzLCBmaWxsID0gTkEsIGNvbG9yID0gImRhcmtncmV5Iiwgc2l6ZSA9IC4xKSArCiAgZ2VvbV90ZXh0KGRhdGEgPSBzdGF0ZXMsIGFlcyhYLCBZLCBsYWJlbCA9IGFiYiksIHNpemUgPSAzKSArCiAgY29vcmRfc2YoeGxpbSA9IGMoLTEyNywgLTYzKSwgCiAgICAgICAgICAgeWxpbSA9IGMoMjQsIDUxKSwgCiAgICAgICAgICAgZXhwYW5kID0gRkFMU0UpICsKICB0aGVtZV9tYXAoKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MoYWxwaGEgPSAuOCwgZGlyZWN0aW9uID0gLTEpICsKICBsYWJzKGZpbGwgPSAiQXJlYSIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKC44NSwgMCkpCmBgYAoKYGBge3IgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NCwgZmlnLmFsaWduPSJjZW50ZXIifQpnZ3Bsb3QoZGF0YSA9IHN0YXRlcykgKwogIGdlb21fc2YoYWVzKGZpbGwgPSBhcmVhKSkgKwogIGdlb21fc2YoZGF0YSA9IGNvdW50aWVzLCBmaWxsID0gTkEsIGNvbG9yID0gImRhcmtncmV5Iiwgc2l6ZSA9IC4xKSArCiAgZ2VvbV90ZXh0KGRhdGEgPSBzdGF0ZXMgJT4lIGZpbHRlcihhYmIgPT0gIk1OIiksIGFlcyhYLCBZLCBsYWJlbCA9IGFiYiksIHNpemUgPSA1KSArCiAgY29vcmRfc2YoeGxpbSA9IGMoLTk4LCAtODkpLCAKICAgICAgICAgICB5bGltID0gYyg0MywgNTApLCAKICAgICAgICAgICBleHBhbmQgPSBGQUxTRSkgKwogIHRoZW1lX21hcCgpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhhbHBoYSA9IC44LCBkaXJlY3Rpb24gPSAtMSkgKwogIGxhYnMoZmlsbCA9ICJBcmVhIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMS4xLCAwKSkKYGBgCgoqKmB0aWR5dGV4dGAgdGFza3MqKjoKCk5vdyB5b3Ugd2lsbCB0cnkgdXNpbmcgdGlkeXRleHQgb24gYSBuZXcgZGF0YXNldCBhYm91dCBSdXNzaWFuIFRyb2xsIHR3ZWV0cy4KCiMjIyMgUmVhZCBhYm91dCB0aGUgZGF0YQoKVGhlc2UgYXJlIHR3ZWV0cyBmcm9tIFR3aXR0ZXIgaGFuZGxlcyB0aGF0IGFyZSBjb25uZWN0ZWQgdG8gdGhlIEludGVybmV0IFJlc2VhcmNoIEFnZW5jeSAoSVJBKSwgYSBSdXNzaWFuICJ0cm9sbCBmYWN0b3J5LiIgIFRoZSBtYWpvcml0eSBvZiB0aGVzZSB0d2VldHMgd2VyZSBwb3N0ZWQgZnJvbSAyMDE1LTIwMTcsIGJ1dCB0aGUgZGF0YXNldHMgZW5jb21wYXNzIHR3ZWV0cyBmcm9tIEZlYnJ1YXJ5IDIwMTIgdG8gTWF5IDIwMTguCgpUaHJlZSBvZiB0aGUgbWFpbiBjYXRlZ29yaWVzIG9mIHRyb2xsIHR3ZWV0IHRoYXQgd2Ugd2lsbCBiZSBmb2N1c2luZyBvbiBhcmUgTGVmdCBUcm9sbHMsIFJpZ2h0IFRyb2xscywgYW5kIE5ld3MgRmVlZC4gICoqTGVmdCBUcm9sbHMqKiB1c3VhbGx5IHByZXRlbmQgdG8gYmUgQkxNIGFjdGl2aXN0cywgYWltaW5nIHRvIGRpdmlkZSB0aGUgZGVtb2NyYXRpYyBwYXJ0eSAoaW4gdGhpcyBjb250ZXh0LCBiZWluZyBwcm8tQmVybmllIHNvIHRoYXQgdm90ZXMgYXJlIHRha2VuIGF3YXkgZnJvbSBIaWxsYXJ5KS4gICoqUmlnaHQgdHJvbGxzKiogaW1pdGF0ZSBUcnVtcCBzdXBwb3J0ZXJzLCBhbmQgKipOZXdzIEZlZWQqKiBoYW5kbGVzIGFyZSAibG9jYWwgbmV3cyBhZ2dyZWdhdG9ycywiIHR5cGljYWxseSBsaW5raW5nIHRvIGxlZ2l0aW1hdGUgbmV3cy4KCkZvciBvdXIgdXBjb21pbmcgYW5hbHlzZXMsIHNvbWUgaW1wb3J0YW50IHZhcmlhYmxlcyBhcmU6CgogICogKiphdXRob3IqKiAoaGFuZGxlIHNlbmRpbmcgdGhlIHR3ZWV0KQogICogKipjb250ZW50KiogKHRleHQgb2YgdGhlIHR3ZWV0KQogICogKipsYW5ndWFnZSoqIChsYW5ndWFnZSBvZiB0aGUgdHdlZXQpCiAgKiAqKnB1Ymxpc2hfZGF0ZSoqIChkYXRlIGFuZCB0aW1lIHRoZSB0d2VldCB3YXMgc2VudCkKClZhcmlhYmxlIGRvY3VtZW50YXRpb24gY2FuIGJlIGZvdW5kIG9uIFtHaXRodWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9maXZldGhpcnR5ZWlnaHQvcnVzc2lhbi10cm9sbC10d2VldHMvKSBhbmQgYSBtb3JlIGRldGFpbGVkIGRlc2NyaXB0aW9uIG9mIHRoZSBkYXRhc2V0IGNhbiBiZSBmb3VuZCBpbiB0aGlzIFtmaXZldGhpcnR5ZWlnaHQgYXJ0aWNsZV0oaHR0cHM6Ly9maXZldGhpcnR5ZWlnaHQuY29tL2ZlYXR1cmVzL3doeS13ZXJlLXNoYXJpbmctMy1taWxsaW9uLXJ1c3NpYW4tdHJvbGwtdHdlZXRzLykuCgpCZWNhdXNlIHRoZXJlIGFyZSAxMiBkYXRhc2V0cyBjb250YWluaW5nIDIsOTczLDM3MSB0d2VldHMgc2VudCBieSAyLDg0OCBUd2l0dGVyIGhhbmRsZXMgaW4gdG90YWwsIHdlIHdpbGwgYmUgdXNpbmcgdGhyZWUgb2YgdGhlc2UgZGF0YXNldHMgKG9uZSBmcm9tIGEgUmlnaHQgdHJvbGwsIG9uZSBmcm9tIGEgTGVmdCB0cm9sbCwgYW5kIG9uZSBmcm9tIGEgTmV3cyBGZWVkIGFjY291bnQpLgoKXApcCgoxLiBSZWFkIGluIFRyb2xsIFR3ZWV0cyBEYXRhc2V0IC0gdGhpcyB0YWtlcyBhIHdoaWxlLiBZb3UgY2FuIGNhY2hlIGl0IHNvIHlvdSBkb24ndCBuZWVkIHRvIHJlYWQgaXQgaW4gYWdhaW4gZWFjaCB0aW1lIHlvdSBrbml0LiBCZSBzdXJlIHRvIHJlbW92ZSB0aGUgYGV2YWw9RkFMU0VgISEhIQoKYGBge3IsIGNhY2hlPVRSVUV9CnRyb2xsX3R3ZWV0cyA8LSByZWFkX2NzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2ZpdmV0aGlydHllaWdodC9ydXNzaWFuLXRyb2xsLXR3ZWV0cy9tYXN0ZXIvSVJBaGFuZGxlX3R3ZWV0c18xMi5jc3YiKQpgYGAKCjIuIEJhc2ljIERhdGEgQ2xlYW5pbmcgYW5kIEV4cGxvcmF0aW9uCgogIGEuIFJlbW92ZSByb3dzIHdoZXJlIHRoZSB0d2VldCB3YXMgaW4gYSBsYW5ndWFnZSBvdGhlciB0aGFuIEVuZ2xpc2gKCmBgYHtyfQp0cm9sbF90d2VldHNfbW9kIDwtIHRyb2xsX3R3ZWV0cyAlPiUKICBmaWx0ZXIobGFuZ3VhZ2UgPT0gIkVuZ2xpc2giKQpgYGAKCiAgYi4gUmVwb3J0IHRoZSBkaW1lbnNpb25zIG9mIHRoZSBkYXRhc2V0CgpgYGB7cn0KZGltKHRyb2xsX3R3ZWV0c19tb2QpCmBgYAogIAogIGMuIENyZWF0ZSB0d28gb3IgdGhyZWUgYmFzaWMgZXhwbG9yYXRvcnkgcGxvdHMgb2YgdGhlIGRhdGEgKGV4LiBwbG90IG9mIHRoZSBkaWZmZXJlbnQgbG9jYXRpb25zIGZyb20gd2hpY2ggdHdlZXRzIHdlcmUgcG9zdGVkLCBwbG90IG9mIHRoZSBhY2NvdW50IGNhdGVnb3J5IG9mIGEgdHdlZXQpCiAgCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02LCBmaWcuYWxpZ249ImNlbnRlciJ9CmcxIDwtIHRyb2xsX3R3ZWV0c19tb2QgJT4lCiAgZ3JvdXBfYnkocmVnaW9uKSAlPiUKICBzdW1tYXJpemUobiA9IG4oKSkgJT4lCiAgZHJvcF9uYSgpICU+JQogIGdncGxvdChhZXMoeCA9IG4sCiAgICAgICAgICAgICB5ID0gZmN0X3Jlb3JkZXIocmVnaW9uLCBuLCAuZGVzYyA9IFRSVUUpKSkgKwogIGdlb21fY29sKGZpbGwgPSAibGlnaHRibHVlIikgKwogIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgdGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDExKSwKICAgICAgICBwbG90LnRpdGxlLnBvc2l0aW9uID0gInBsb3QiLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpICsKICBzY2FsZV94X2xvZzEwKCkgKwogIGxhYnModGl0bGUgPSAiTnVtYmVyIG9mIFR3ZWV0cyBieSBSZWdpb24iKQoKZzIgPC0gdHJvbGxfdHdlZXRzX21vZCAlPiUKICBncm91cF9ieShhY2NvdW50X2NhdGVnb3J5KSAlPiUKICBzdW1tYXJpemUobiA9IG4oKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gbiwKICAgICAgICAgICAgIHkgPSBmY3RfcmVvcmRlcihhY2NvdW50X2NhdGVnb3J5LCBuLCAuZGVzYyA9IFRSVUUpKSkgKwogIGdlb21fY29sKGZpbGwgPSAibGlnaHRibHVlIikgKwogIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC50aXRsZS5wb3NpdGlvbiA9ICJwbG90IiwKICAgICAgICB0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEpLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpICsKICBsYWJzKHRpdGxlID0gIk51bWJlciBvZiBUd2VldHMgYnkgQWNjb3VudCBDYXRlZ29yeSIpCgpncmlkLmFycmFuZ2UoZzEsIGcyLCBuY29sID0gMikKYGBgCgoKMy4gVW5uZXN0IFRva2VucwoKV2Ugd2FudCBlYWNoIHJvdyB0byByZXByZXNlbnQgYSB3b3JkIGZyb20gYSB0d2VldCwgcmF0aGVyIHRoYW4gYW4gZW50aXJlIHR3ZWV0LiBCZSBzdXJlIHRvIHJlbW92ZSB0aGUgYGV2YWw9RkFMU0VgISEhIQoKYGBge3J9Cih0cm9sbF90d2VldHNfdW50b2tlbiA8LSB0cm9sbF90d2VldHNfbW9kICU+JQogICB1bm5lc3RfdG9rZW5zKG91dHB1dCA9IHdvcmQsIGlucHV0ID0gY29udGVudCkpCmBgYAoKNC4gUmVtb3ZlIHN0b3B3b3Jkcy4gQmUgc3VyZSB0byByZW1vdmUgdGhlIGBldmFsPUZBTFNFYCEhISEKCmBgYHtyfQojIGdldCByaWQgb2Ygc3RvcHdvcmRzICh0aGUsIGFuZCwgZXRjLikKdHJvbGxfdHdlZXRzX2NsZWFuZWQgPC0gdHJvbGxfdHdlZXRzX3VudG9rZW4gJT4lCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpCmBgYAoKVGFrZSBhIGxvb2sgYXQgdGhlIHRyb2xsX3R3ZWV0c19jbGVhbmVkIGRhdGFzZXQuICBBcmUgdGhlcmUgYW55IG90aGVyIHdvcmRzL2xldHRlcnMvbnVtYmVycyB0aGF0IHdlIHdhbnQgdG8gZWxpbWluYXRlIHRoYXQgd2VyZW4ndCB0YWtlbiBjYXJlIG9mIGJ5IHN0b3Bfd29yZHM/IEJlIHN1cmUgdG8gcmVtb3ZlIHRoZSBgZXZhbD1GQUxTRWAhISEhCgpgYGB7cn0KdHJvbGxfdHdlZXRzX2NsZWFuZWQgJT4lCiAgY291bnQod29yZCwgbmFtZSA9ICJjb3VudCIpICU+JQogIGFycmFuZ2UoZGVzYyhjb3VudCkpCmBgYAoKYGBge3J9CiMgZ2V0IHJpZCBvZiBodHRwLCBodHRwcywgdC5jbywgcnQsIGFtcCwgc2luZ2xlIG51bWJlciBkaWdpdHMsIGFuZCBzaW5ndWxhciBsZXR0ZXJzCnRyb2xsX3R3ZWV0c19jbGVhbmVkIDwtIHRyb2xsX3R3ZWV0c19jbGVhbmVkICU+JQogIGZpbHRlcighKHdvcmQgJWluJSBjKCJodHRwIiwgImh0dHBzIiwgInQuY28iLCAicnQiLCAiYW1wIikpKSAlPiUKICBmaWx0ZXIobmNoYXIod29yZCkgPiAxKQpgYGAKCgo1LiBMb29rIGF0IGEgc3Vic2V0IG9mIHRoZSB0d2VldHMgdG8gc2VlIGhvdyBvZnRlbiB0aGUgdG9wIHdvcmRzIGFwcGVhci4KCmBgYHtyfQp0cm9sbF90d2VldHNfc21hbGwgPC0gdHJvbGxfdHdlZXRzX2NsZWFuZWQgJT4lCiAgY291bnQod29yZCkgJT4lCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gbiwgbiA9IDUwKSAjIDUwIG1vc3Qgb2NjdXJyaW5nIHdvcmRzCgojIHZpc3VhbGl6ZSB0aGUgbnVtYmVyIG9mIHRpbWVzIHRoZSA1MCB0b3Agd29yZHMgYXBwZWFyCmdncGxvdCh0cm9sbF90d2VldHNfc21hbGwsIAogICAgICAgYWVzKHkgPSBmY3RfcmVvcmRlcih3b3JkLG4pLCB4ID0gbikpICsKICBnZW9tX2NvbCgpICsKICB0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3QudGl0bGUucG9zaXRpb24gPSAicGxvdCIpICsKICBsYWJzKHRpdGxlID0gIk51bWJlciBvZiBUaW1lcyB0aGUgVG9wIDUwIE1vc3QgQ29tbW9uIFdvcmRzIEFwcGVhcmVkIikKYGBgCgoKNi4gU2VudGltZW50IEFuYWx5c2lzCgogIGEuIEdldCB0aGUgc2VudGltZW50cyB1c2luZyB0aGUgImJpbmciIHBhcmFtZXRlciAod2hpY2ggY2xhc3NpZmllcyB3b3JkcyBpbnRvICJwb3NpdGl2ZSIgb3IgIm5lZ2F0aXZlIikKICAKYGBge3J9CiMgU2VudGltZW50cyAKc2VudGltZW50cyA8LSBnZXRfc2VudGltZW50cygiYmluZyIpCmBgYAoKICBiLiBSZXBvcnQgaG93IG1hbnkgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIHdvcmRzIHRoZXJlIGFyZSBpbiB0aGUgZGF0YXNldC4gIEFyZSB0aGVyZSBtb3JlIHBvc2l0aXZlIG9yIG5lZ2F0aXZlIHdvcmRzLCBhbmQgd2h5IGRvIHlvdSB0aGluayB0aGlzIG1pZ2h0IGJlPwogIApCZSBzdXJlIHRvIHJlbW92ZSB0aGUgYGV2YWw9RkFMU0VgISEhIQoKYGBge3J9CiMgYXNzaWduIGEgc2VudGltZW50IHRvIGVhY2ggd29yZCB0aGF0IGhhcyBvbmUgYXNzb2NpYXRlZAp0cm9sbF90d2VldHNfc2VudGltZW50IDwtIHRyb2xsX3R3ZWV0c19jbGVhbmVkICU+JQogIGlubmVyX2pvaW4oc2VudGltZW50cykKCiMgY291bnQgdGhlIHNlbnRpbWVudHMKdHJvbGxfdHdlZXRzX3NlbnRpbWVudCAlPiUgCiAgY291bnQoc2VudGltZW50KQpgYGAKCioqVGhlcmUgYXJlIG1vcmUgbmVnYXRpdmUgd29yZHMuIElmIHRoZSB0cm9sbHMgd2VyZSwgd2VsbCwgdHJvbGxpbmcgdGhlbiB0aGV5IG11c3QgaGF2ZSB0cmllZCB0byBiZSBhcyBwb2xhcml6aW5nIGFzIHBvc3NpYmxlIGFuZCB0aGVyZWZvcmUgdGhlaXIgdHdlZXRzIHdlcmUgbG9hZGVkIHdpdGggbW9yZSBuZWdhdGl2ZSBzZW50aW1lbnRzLioqCgo3LiBVc2luZyB0aGUgdHJvbGxfdHdlZXRzX3NtYWxsIGRhdGFzZXQsIG1ha2UgYSB3b3JkY2xvdWQ6CgogIGEuIFRoYXQgaXMgc2l6ZWQgYnkgdGhlIG51bWJlciBvZiB0aW1lcyB0aGF0IGEgd29yZCBhcHBlYXJzIGluIHRoZSB0d2VldHMKICAKYGBge3J9CiMgbWFrZSBhIHdvcmRjbG91ZCB3aGVyZSB0aGUgc2l6ZSBvZiB0aGUgd29yZCBpcyBiYXNlZCBvbiB0aGUgbnVtYmVyIG9mIHRpbWVzIHRoZSB3b3JkIGFwcGVhcnMgYWNyb3NzIHRoZSB0d2VldHMKdHJvbGxfdHdlZXRzX3NtYWxsICU+JQogIHdpdGgod29yZGNsb3VkKHdvcmQsIG4pKQpgYGAKCiAgYi4gVGhhdCBpcyBjb2xvcmVkIGJ5IHNlbnRpbWVudCAocG9zaXRpdmUgb3IgbmVnYXRpdmUpCgpgYGB7cn0KIyBtYWtlIGEgd29yZGNsb3VkIGNvbG9yZWQgYnkgc2VudGltZW50CnRyb2xsX3R3ZWV0c19zZW50aW1lbnQgJT4lCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lCiAgYWNhc3Qod29yZCB+IHNlbnRpbWVudCwgdmFsdWUudmFyID0gIm4iLCBmaWxsID0gMCkgJT4lCiAgY29tcGFyaXNvbi5jbG91ZChjb2xvcnMgPSBjKCJyZWQiLCAiZ3JlZW4iKSwKICAgICAgICAgICAgICAgICAgIG1heC53b3JkcyA9IDUwKQpgYGAKCkFyZSB0aGVyZSBhbnkgd29yZHMgd2hvc2UgY2F0ZWdvcml6YXRpb24gYXMgInBvc2l0aXZlIiBvciAibmVnYXRpdmUiIHN1cnByaXNlZCB5b3U/CgoqKldlbGwgYXNpZGUgZm9yIHRoZSBiaWcgZ3JlZW4gInRydW1wIiB3b3JkIGluIHRoZSBtaWRkbGUgb2YgdGhlIHdvcmRjbG91ZCwgSSdtIG5vdCByZWFsbHkgc3VycHJpc2VkIGJ5IHRoZSByZXN1bHRzLiBUZWNobmljYWxseSB0aGUgd29yZCBkb2VzIGhhdmUgYSBwb3NpdGl2ZSBtZWFuaW5nLCBidXQgaW4gdGhlIGNvbnRleHQgb2YgdGhpbmdzIGl0IGlzIGtpbmRhIGZ1bm55IGFuZCBzYWQgYXQgdGhlIHNhbWUgdGltZS4qKgoKIyMgUHJvamVjdHMKClJlYWQgdGhlIHByb2plY3QgZGVzY3JpcHRpb24gb24gdGhlIG1vb2RsZSBwYWdlLiBUYWxrIHRvIHlvdXIgZ3JvdXAgbWVtYmVycyBhYm91dCBwb3RlbnRpYWwgdG9waWNzLiAKCioqVGFzazoqKgoKV3JpdGUgYSBzaG9ydCBwYXJhZ3JhcGggYWJvdXQgaWRlYXMgeW91IGhhdmUuIElmIHlvdSBhbHJlYWR5IGhhdmUgc29tZSBkYXRhIHNvdXJjZXMgaW4gbWluZCwgeW91IGNhbiBsaW5rIHRvIHRob3NlLCBidXQgSSdtIG1vcmUgY29uY2VybmVkIHdpdGggeW91IGhhdmluZyBhIHRvcGljIHRoYXQgeW91J3JlIGludGVyZXN0ZWQgaW4gaW52ZXN0aWdhdGluZyByaWdodCBub3cuIAoKKipPdXIgZ3JvdXAgaGFzIG5hcnJvd2VkIGRvd24gdG8gdHdvIGJyb2FkIGlkZWFzOiBLLTEyIGNvbXB1dGVyIHNjaWVuY2UgZWR1Y2F0aW9uIGFuZCBtZW50YWwgaGVhbHRoIHJlc291cmNlcy4gV2UncmUgaW50ZXJlc3RlZCBpbiB0aGUgYXZhaWxhYmlsaXR5IGFuZCBtYXliZSBxdWFsaXR5IG9mIGVhY2ggc28gdGhlIHByb2plY3QgbWlnaHQgbGVhbiBtb3JlIHRvd2FyZHMgdmlzdWFsaXphdGlvbiBhcyBvcHBvc2VkIHRvIG1vZGVsaW5nIGRlcGVuZGluZyBvbiB0aGUgZGF0YSB3ZSBhY3F1aXJlLiBXZSdsbCBhbHNvIGJyYW5jaCBvdXQgZHVyaW5nIG91ciBpbmRpdmlkdWFsIGRhdGEgc2VhcmNoIGFuZCBhcmUgc3RpbGwgb3BlbiB0byBpZGVhcyBpZiB3ZSBjb21lIGFjcm9zcyBpbnRlcmVzdGluZyBkYXRhIHRoYXQgYXJlIHJlbGF0ZWQgdG8gQ1MgZWR1Y2F0aW9uIG9yIG1lbnRhbCBoZWFsdGguKioKCiMjICJVbmRvaW5nIiBiaWFzCgoqKlRhc2s6KioKClJlYWQgdGhpcyB0d2VldCBbdGhyZWFkXShodHRwczovL3RocmVhZHJlYWRlcmFwcC5jb20vdGhyZWFkLzEzNzU5NTcyODQwNjEzNzY1MTYuaHRtbCkgYnkgW0RlYiBSYWppXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9EZWJvcmFoX1JhamkpIHdobyB5b3UgbWF5IHJlbWVtYmVyIGZyb20gdGhlICpDb2RlZCBCaWFzKiBmaWxtLiBXcml0ZSBhIHNob3J0IHBhcmFncmFwaCB0aGF0IGRpc2N1c3NlcyBhdCBsZWFzdCBvbmUgb2YgdGhlIG1pc2NvbmNlcHRpb25zLgoKKipJIHRoaW5rIHRoZSBsYXN0IG1pc2NvbmNlcHRpb24gcmVhbGx5IGRyaXZlcyBob21lIHRoZSBudWFuY2UgYW5kIGNoYWxsZW5nZSBvZiBhZGRyZXNzaW5nIGFuZCB0YWNrbGluZyBiaWFzIGluIGFsZ29yaXRobXMuIEZpcnN0IG9mZiB0aGUgcHJvYmxlbSBpdHNlbGYgaXMgaGFyZCB0byBpZGVudGlmeS4gUmFjaWFsIGFuZCBnZW5kZXIgYmlhc2VzIGFyZSBzbyBpbmdyYWluZWQgYW5kIGJsZWVkIGludG8gbWFueSBkaWZmZXJlbnQgdmFyaWFibGVzICh0aGF0IHBlcnRhaW4gdG8gc29jaW8tZWNvbm9taWMgc3RhdHVzLCBmb3IgZXhhbXBsZSksIHRoYXQgZXZlbiB3aGVuIHRoZSBhbGdvcml0aG1zIGRvbid0IGV4cGxpY2l0bHkgaW5jbHVkZSByYWNlIGFuZCBnZW5kZXIgYXMgY292YXJpYXRlcywgc3VjaCBiaWFzZXMgd2lsbCBzdGlsbCBiZSBwcmVzZW50LiBBbHRob3VnaCB3ZSBjYW4gdGVzdCB0aGUgc3lzdGVtIG9uIHZhcmlvdXMgcG9wdWxhdGlvbnMgYW5kIGNvbXBhcmUgaXRzIGFjY3VyYWN5IGFuZCBmYWlybmVzcywgaXQgd291bGQgYmUgaGFyZCB0byBwaW5wb2ludCBleGFjdGx5IHdoZXJlIGJpYXMgd2FzIGludHJvZHVjZWQsIGxldCBhbG9uZSBhZGRyZXNzIGl0LiBBcyBEZWIgUmFqaSBwb2ludGVkIG91dCBhdCB0aGUgYmVnaW5uaW5nLCBiaWFzZXMgY2FuIGFyaXNlIGF0IGFueSBwb2ludCBhbmQgYnkgYW55IGRlc2lnbiBkZWNpc2lvbi4gVGhpcyBub3Qgb25seSBtYWtlcyB0aGVtIG9ic2N1cmUgYW5kIGhhcmQgdG8gZGV0ZWN0IGJ1dCBhbHNvIG5hdHVyYWxseSBsZWFkcyB0byB0aGUgcG9zc2liaWxpdHkgb2YgaW50cm9kdWNpbmcgb3RoZXIgYmlhc2VzIHdoZW4gcGVvcGxlIHRyeSB0byAiZml4IiB0aGUgaXNzdWVzLiBUaGVyZWZvcmUsIHRoZSBjaGFsbGVuZ2Ugb2YgY3JlYXRpbmcgYW5kIGZyYW1pbmcgaW52ZW50aW9ucyBpcyBtYWRlIGhhcmRlciBhcyB0aGVyZSBpcyBubyBkZWZpbml0aXZlIHBvaW50IHdoZXJlIHBlb3BsZSBjYW4gc3RvcCBhbmQgY29uY2x1ZGUgdGhhdCB0aGUgc3lzdGVtIGlzIGNvbXBsZXRlbHkgcmVsaWFibGUgb3IgZmFpci4gSWYgd2UgZGlkIGZyYW1lIHRoZW0gYXMgc3VjaCwgdGhlbiB3ZSdkIGZhbGwgYmFjayBpbnRvIHRoZSB0cmFwIG9mIGJsaW5kbHkgdXNpbmcgaXQgd2l0aG91dCBmdXJ0aGVyIHF1ZXN0aW9uIG9yIGV2YWx1YXRpb24uIFVsdGltYXRlbHkgdGhlc2Ugc3lzdGVtcyBjYW4gbmV2ZXIgYmUgZmxhd2xlc3MgYW5kIGFzIHdlIGNvbGxlY3QgbW9yZSBkYXRhIGFuZCBhcyBzb2NpZXR5IHByb2dyZXNzZXMsIHRoZXkgbmVlZCB0byBiZSBjb25zdGFudGx5IG1vbml0b3JlZCB0byBlbnN1cmUgdGhhdCB0aGVpciB1c2UgYW5kIHJlc3VsdHMgYXJlIHN0aWxsIGFwcHJvcHJpYXRlIGFuZCBhcHBsaWNhYmxlIHRvIHRoZSB0YXJnZXQgcG9wdWxhdGlvbi4qKgo=